From e52d9a65293a733f91426142c6466909850a065e Mon Sep 17 00:00:00 2001 From: Michael James Date: Thu, 25 May 2017 21:21:32 +1000 Subject: [PATCH 1/2] Updated build status and coverage link to point to new org * Now points to devlucky --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c7ef8d8..a196c2c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/majames/react-audio-vis.svg?branch=master)](https://travis-ci.org/majames/react-audio-vis) -[![Coverage Status](https://coveralls.io/repos/github/majames/react-audio-vis/badge.svg?branch=chore%2Fcoverage-stats)](https://coveralls.io/github/majames/react-audio-vis?branch=chore%2Fcoverage-stats) +[![Build Status](https://travis-ci.org/devlucky/react-audio-vis.svg?branch=master)](https://travis-ci.org/devlucky/react-audio-vis) +[![Coverage Status](https://coveralls.io/repos/github/devlucky/react-audio-vis/badge.svg)](https://coveralls.io/github/devlucky/react-audio-vis) *This library is not ready for consumption* \ No newline at end of file From b49a1f98c464e0056c6610e6ccaf3ee6fd42d08e Mon Sep 17 00:00:00 2001 From: Michael James Date: Thu, 25 May 2017 21:53:56 +1000 Subject: [PATCH 2/2] Wrote tests for AudioBars component --- package.json | 2 + src/analyser/index.test.tsx | 45 +++++++++++- src/analyser/index.tsx | 24 ++++++ src/bars/index.test.tsx | 104 ++++++++++++++++++++++++++ src/bars/index.tsx | 28 ++----- src/bars/styled.tsx | 2 + yarn.lock | 142 ++++++++++++++++++++++++++++++++++-- 7 files changed, 316 insertions(+), 31 deletions(-) create mode 100644 src/bars/index.test.tsx diff --git a/package.json b/package.json index dac30bb..7b4ef9b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "devDependencies": { "@kadira/storybook": "^2.21.0", "coveralls": "^2.13.1", + "enzyme": "^2.8.2", "react-scripts-ts": "^1.4.0", + "react-test-renderer": "^15.5.4", "styled-components": "^1.4.6", "typescript": "^2.3.2" }, diff --git a/src/analyser/index.test.tsx b/src/analyser/index.test.tsx index 45d65a1..1668ee8 100644 --- a/src/analyser/index.test.tsx +++ b/src/analyser/index.test.tsx @@ -5,9 +5,14 @@ interface MockNode { disconnect: Function; } +interface MockAnalyserNode extends MockNode { + frequencyBinCount: number; + getByteFrequencyData: (array: Int8Array) => void; +} + describe('Analyser', () => { let sourceNode: MockNode; - let analyserNode: MockNode; + let analyserNode: MockAnalyserNode; let destinationNode: MockNode; let audioContext: AudioContext; let audioEl: HTMLAudioElement; @@ -21,7 +26,9 @@ describe('Analyser', () => { analyserNode = { connect: jest.fn(), - disconnect: jest.fn() + disconnect: jest.fn(), + frequencyBinCount: 10, + getByteFrequencyData: () => {} }; destinationNode = { @@ -77,4 +84,38 @@ describe('Analyser', () => { expect(analyserNode.disconnect).toHaveBeenCalledTimes(1); }); }); + + describe('getBucketedByteFrequencyData', () => { + const mockAnalyserNodeWithFrequencies = (analyserNodeFrequencies: Array) => { + analyserNode.frequencyBinCount = analyserNodeFrequencies.length; + + analyserNode.getByteFrequencyData = jest.fn().mockImplementation( + (dataArray) => { + for (let i = 0; i < analyserNodeFrequencies.length; i++) { + dataArray[i] = analyserNodeFrequencies[i]; + } + } + ); + }; + + it('returns the unaltered array when there are more buckets than array entries', () => { + const numberOfBuckets = 10; + const analyserNodeFrequencies = [1, 2, 3, 4]; + mockAnalyserNodeWithFrequencies(analyserNodeFrequencies); + + const bucketedArray = analyser.getBucketedByteFrequencyData(numberOfBuckets); + expect(bucketedArray).toEqual(analyserNodeFrequencies); + }); + + it('correctly buckets array when there are less buckets than array entries', () => { + const numberOfBuckets = 4; + const analyserNodeFrequencies = [1, 2, 3, 4, 5, 6, 7, 8]; + const expectedBucketArray = [1.5, 3.5, 5.5, 7.5]; + + mockAnalyserNodeWithFrequencies(analyserNodeFrequencies); + + const bucketedArray = analyser.getBucketedByteFrequencyData(numberOfBuckets); + expect(bucketedArray).toEqual(expectedBucketArray); + }); + }); }); diff --git a/src/analyser/index.tsx b/src/analyser/index.tsx index 79d900a..6feace5 100644 --- a/src/analyser/index.tsx +++ b/src/analyser/index.tsx @@ -1,3 +1,6 @@ +import chunk = require('lodash.chunk'); +import sum = require('lodash.sum'); + export interface AnalyserSpec { audioEl: HTMLAudioElement; audioContext: AudioContext; @@ -13,6 +16,7 @@ export class Analyser { audioEl: HTMLAudioElement; analyserNode: AnalyserNode; private source: MediaElementAudioSourceNode; + private dataArray: Uint8Array; constructor(spec: AnalyserSpec) { this.audioEl = spec.audioEl; @@ -25,6 +29,26 @@ export class Analyser { if (source) { source.disconnect(); } } + getBucketedByteFrequencyData = (maxNumBuckets: number): Array => { + const {analyserNode} = this; + + const bufferLength = analyserNode.frequencyBinCount; + if (!this.dataArray) { + this.dataArray = new Uint8Array(bufferLength); + } + + const {dataArray} = this; + analyserNode.getByteFrequencyData(dataArray); + + const numBuckets = Math.min(dataArray.length, maxNumBuckets); + + // bucket values + const numValuesPerChunk = Math.ceil(bufferLength / numBuckets); + const chunkedData = chunk(dataArray, numValuesPerChunk); + + return chunkedData.map((arr: Array) => sum(arr) / arr.length); + } + private createAnalyserNode = ({audioEl, audioContext}: AnalyserSpec): void => { this.source = audioContext.createMediaElementSource(audioEl); diff --git a/src/bars/index.test.tsx b/src/bars/index.test.tsx new file mode 100644 index 0000000..4368714 --- /dev/null +++ b/src/bars/index.test.tsx @@ -0,0 +1,104 @@ +import * as React from 'react'; +import {shallow} from 'enzyme'; + +import {AudioBars} from './'; + +describe('AudioBars', () => { + describe('componentDidMount', () => { + it('registers event listeners', () => { + const audioEl = {addEventListener: jest.fn()}; + const analyser = {audioEl} as any; + + const wrapper = shallow(); + wrapper.instance().componentDidMount(); + + const {addEventListener} = audioEl; + expect(addEventListener).toHaveBeenCalledTimes(3); + expect(addEventListener.mock.calls[0][0]).toEqual('playing'); + expect(addEventListener.mock.calls[1][0]).toEqual('pause'); + expect(addEventListener.mock.calls[2][0]).toEqual('ended'); + }); + }); + + describe('render', () => { + it('default width passed to BarsCanvas is 400px', () => { + const analyser = {} as any; + const wrapper = shallow(); + + expect(wrapper.props().width).toEqual(400); + }); + + it('default height passed to BarsCanvas is 400px', () => { + const analyser = {} as any; + const wrapper = shallow(); + + expect(wrapper.props().height).toEqual(400); + }); + + it('passes dimensions to BarsCanvas', () => { + const analyser = {} as any; + const dims = {width: 200, height: 200}; + const wrapper = shallow(); + + expect(wrapper.props().height).toEqual(dims.height); + expect(wrapper.props().width).toEqual(dims.width); + }); + }); + + describe('drawBars', () => { + let freqData: Array; + let analyser; + let canvasContext; + + beforeEach(() => { + window.requestAnimationFrame = jest.fn(); + + freqData = [1, 2, 3, 4]; + analyser = { + getBucketedByteFrequencyData: jest.fn().mockReturnValue(freqData) + } as any; + + canvasContext = {clearRect: jest.fn(), fillRect: jest.fn()}; + }); + + it('clears the canvas', () => { + const canvasDimensions = {width: 100, height: 200}; + const wrapper = shallow(); + + wrapper.instance().canvasContext = canvasContext; + wrapper.instance().drawBars(); + + expect(canvasContext.clearRect).toHaveBeenCalledTimes(1); + expect(canvasContext.clearRect).toHaveBeenLastCalledWith( + 0 , 0, canvasDimensions.width, canvasDimensions.height + ); + }); + + it('draws the correct number of bars to the canvas', () => { + const wrapper = shallow(); + + wrapper.instance().canvasContext = canvasContext; + wrapper.instance().drawBars(); + + expect(canvasContext.fillRect).toHaveBeenCalledTimes(freqData.length); + }); + + it('draws the first bar with the correct dimensions to the canvas', () => { + const canvasDimensions = {width: 100, height: 200}; + const wrapper = shallow(); + + wrapper.instance().canvasContext = canvasContext; + wrapper.instance().drawBars(); + + const oneByte = 256; + const firstBarHeight = (1 / oneByte) * canvasDimensions.height; + const firstBarWidth = canvasDimensions.width / freqData.length; + expect(canvasContext.fillRect).toHaveBeenCalledWith( + 0, + canvasDimensions.height - firstBarHeight, + firstBarWidth, + firstBarHeight + ); + }); + }); +}); diff --git a/src/bars/index.tsx b/src/bars/index.tsx index 00ea66d..db6e5a3 100644 --- a/src/bars/index.tsx +++ b/src/bars/index.tsx @@ -1,7 +1,5 @@ import * as React from 'react'; import {Component} from 'react'; -import chunk = require('lodash.chunk'); -import sum = require('lodash.sum'); import {Analyser} from '../analyser'; import {Dimensions} from '../utils/dimensions'; @@ -16,7 +14,6 @@ export class AudioBars extends Component { private canvasEl: HTMLCanvasElement; private canvasContext: CanvasRenderingContext2D; private animationId: number; - private dataArray: Uint8Array; componentDidMount() { const {audioEl} = this.props.analyser; @@ -60,35 +57,22 @@ export class AudioBars extends Component { } this.canvasContext = context; - - const {analyserNode} = this.props.analyser; - const bufferLength = analyserNode.frequencyBinCount; - this.dataArray = new Uint8Array(bufferLength); - this.drawBars(); } private drawBars = (): void => { - const {canvasContext, width: canvasWidth, height: canvasHeight, dataArray} = this; + const {canvasContext, width: canvasWidth, height: canvasHeight} = this; + const {analyser} = this.props; // clear the canvas this.canvasContext.clearRect(0, 0, canvasWidth, canvasHeight); - const {analyserNode} = this.props.analyser; - const maxByteValue = 256; - analyserNode.getByteFrequencyData(dataArray); - - const numBarsToDraw = Math.min(dataArray.length, 64); - const bufferLength = dataArray.length; - - // chunk values if too many to display - const numValuesPerChunk = Math.ceil(bufferLength / numBarsToDraw); - const chunkedData = chunk(dataArray, numValuesPerChunk); - const barValues = chunkedData.map((arr: Array) => sum(arr) / arr.length); - - const barWidth = canvasWidth / numBarsToDraw; + const maxNumBarsToDraw = 64; + const barValues = analyser.getBucketedByteFrequencyData(maxNumBarsToDraw); + const barWidth = canvasWidth / barValues.length; // draw the bars + const maxByteValue = 256; for (let i = 0; i < barValues.length; i++) { const x = i * barWidth + i; diff --git a/src/bars/styled.tsx b/src/bars/styled.tsx index 6deb336..f9d540d 100644 --- a/src/bars/styled.tsx +++ b/src/bars/styled.tsx @@ -4,3 +4,5 @@ export const BarsCanvas = styled.canvas` border: 2px solid blue; border-radius: 3px; `; + +BarsCanvas.displayName = 'BarsCanvas'; diff --git a/yarn.lock b/yarn.lock index 1ba165f..08bd1ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1287,6 +1287,27 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +cheerio@^0.22.0: + version "0.22.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash.assignin "^4.0.9" + lodash.bind "^4.1.4" + lodash.defaults "^4.0.1" + lodash.filter "^4.4.0" + lodash.flatten "^4.2.0" + lodash.foreach "^4.3.0" + lodash.map "^4.4.0" + lodash.merge "^4.4.0" + lodash.pick "^4.2.1" + lodash.reduce "^4.4.0" + lodash.reject "^4.4.0" + lodash.some "^4.4.0" + chokidar@^1.0.0: version "1.6.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" @@ -1648,7 +1669,7 @@ css-loader@0.26.1, css-loader@^0.26.1: postcss-modules-values "^1.1.0" source-list-map "^0.1.4" -css-select@^1.1.0: +css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" dependencies: @@ -1859,7 +1880,7 @@ dom-converter@~0.1: dependencies: utila "~0.3" -dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: @@ -1870,7 +1891,7 @@ domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" -domelementtype@1: +domelementtype@1, domelementtype@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" @@ -1884,13 +1905,19 @@ domhandler@2.1: dependencies: domelementtype "1" +domhandler@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + dependencies: + domelementtype "1" + domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" dependencies: domelementtype "1" -domutils@1.5.1: +domutils@1.5.1, domutils@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -1970,10 +1997,25 @@ enhanced-resolve@~0.9.0: memory-fs "^0.2.0" tapable "^0.1.8" -entities@~1.1.1: +entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" +enzyme@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.8.2.tgz#6c8bcb05012abc4aa4bc3213fb23780b9b5b1714" + dependencies: + cheerio "^0.22.0" + function.prototype.name "^1.0.0" + is-subset "^0.1.1" + lodash "^4.17.2" + object-is "^1.0.1" + object.assign "^4.0.4" + object.entries "^1.0.3" + object.values "^1.0.3" + prop-types "^15.5.4" + uuid "^2.0.3" + "errno@>=0.1.1 <0.2.0-0", errno@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" @@ -2350,6 +2392,14 @@ function-bind@^1.0.2, function-bind@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" +function.prototype.name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.0.0.tgz#5f523ca64e491a5f95aba80cc1e391080a14482e" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + is-callable "^1.1.2" + fuse.js@^2.2.0: version "2.7.4" resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-2.7.4.tgz#96e420fde7ef011ac49c258a621314fe576536f9" @@ -2591,6 +2641,17 @@ html-webpack-plugin@2.24.0: pretty-error "^2.0.2" toposort "^1.0.0" +htmlparser2@^3.9.1: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -2749,7 +2810,7 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -is-callable@^1.1.1, is-callable@^1.1.3: +is-callable@^1.1.1, is-callable@^1.1.2, is-callable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" @@ -2888,6 +2949,10 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + is-svg@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" @@ -3428,6 +3493,14 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" +lodash.assignin@^4.0.9: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" + +lodash.bind@^4.1.4: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -3443,6 +3516,22 @@ lodash.clonedeep@^3.0.0: lodash._baseclone "^3.0.0" lodash._bindcallback "^3.0.0" +lodash.defaults@^4.0.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + +lodash.filter@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" + +lodash.flatten@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + +lodash.foreach@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -3459,14 +3548,34 @@ lodash.keys@^3.0.0, lodash.keys@^3.1.2: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash.map@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" +lodash.merge@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + lodash.pick@^4.2.0, lodash.pick@^4.2.1, lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" +lodash.reduce@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" + +lodash.reject@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" + +lodash.some@^4.4.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -3858,10 +3967,22 @@ object-assign@4.1.1, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^ version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" -object-keys@^1.0.8: +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + +object-keys@^1.0.10, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object.assign@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.0" + object-keys "^1.0.10" + object.entries@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" @@ -4631,6 +4752,13 @@ react-stubber@^1.0.0: dependencies: babel-runtime "^6.5.0" +react-test-renderer@^15.5.4: + version "15.5.4" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.5.4.tgz#d4ebb23f613d685ea8f5390109c2d20fbf7c83bc" + dependencies: + fbjs "^0.8.9" + object-assign "^4.1.0" + react@^15.5.4: version "15.5.4" resolved "https://registry.yarnpkg.com/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047"