From 987a85794d6f40e8e9b572bbe1ef3f36a664551a Mon Sep 17 00:00:00 2001 From: David Atchley Date: Wed, 15 Mar 2017 13:23:10 -0500 Subject: [PATCH] fix: unit test fixes w/ ugly manual mock --- .babelrc | 5 +- .eslintrc | 4 +- package.json | 6 ++- src/__mocks__/utils.js | 94 ++++++++++++++++++++++++++++++++++++ src/__tests__/index.tests.js | 29 ++++++++--- src/__tests__/utils.tests.js | 46 +----------------- src/index.js | 18 ++----- src/utils.js | 5 ++ 8 files changed, 136 insertions(+), 71 deletions(-) create mode 100644 src/__mocks__/utils.js diff --git a/.babelrc b/.babelrc index b00b3d6..47c9ace 100755 --- a/.babelrc +++ b/.babelrc @@ -1 +1,4 @@ -{ "presets": ["es2015", "react"] } +{ + "presets": ["es2015", "react"], + "plugins": ["transform-object-rest-spread"] +} diff --git a/.eslintrc b/.eslintrc index 47c8033..6e5b917 100755 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,6 @@ { "extends": "eslint-config-airbnb", + "parser": "babel-eslint", "env": { "browser": true, "jest": true, @@ -10,7 +11,8 @@ "react/jsx-uses-vars": 2, "react/react-in-jsx-scope": 2, "comma-dangle": 0, - "brace-style": ["error", "stroustrup", { "allowSingleLine": true }] + "brace-style": ["error", "stroustrup", { "allowSingleLine": true }], + "space-before-function-paren": 0 }, "plugins": [ "react" diff --git a/package.json b/package.json index 634ac1d..8a81bf7 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "build:umd": "webpack && NODE_ENV=production webpack", "build:analyze": "webpack --json > ./lib/build-stats.json", "lint": "eslint src examples", - "test": "jest", - "test:watch": "jest --watch", + "test": "jest --env=jsdom", + "test:watch": "jest --env=jsdom --watch", "test:cov": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", "prepublish": "npm run lint && npm run test && npm run clean && npm run build && npm run build:umd", "release": "standard-version" @@ -33,8 +33,10 @@ "ava": "^0.16.0", "babel-cli": "^6.4.5", "babel-core": "^6.4.5", + "babel-eslint": "^6.1.2", "babel-jest": "^10.0.2", "babel-loader": "^6.2.1", + "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "co-exec": "^1.0", diff --git a/src/__mocks__/utils.js b/src/__mocks__/utils.js new file mode 100644 index 0000000..fa86084 --- /dev/null +++ b/src/__mocks__/utils.js @@ -0,0 +1,94 @@ +// __mocks__/utils.js +import values from 'lodash/values'; + +const utils = require.requireActual('../utils'); + +const elementMap = {}; + +// Takes an object with the following properties +// { 'parent': { width, height, top, left }, ... } +const __setMockElements = (mapping) => { + /* eslint-disable no-param-reassign */ + const elements = Object.keys(mapping).reduce((acc, key) => { + const styles = mapping[key].styles || {}; + acc[key] = Object.assign({}, mapping[key], { + clientWidth: mapping[key].width || 0, + clientHeight: mapping[key].height || 0, + offsetWidth: mapping[key].width || 0, + offsetHeight: mapping[key].height || 0, + scrollWidth: mapping[key].width || 0, + scrollHeight: mapping[key].height || 0, + _styles: {}, + style: { + lineHeight: '1', + fontSize: '16px', + ...styles + }, + getBoundingClientRect() { + return { + top: mapping[key].top || 0, + bottom: this.offsetHeight, + left: mapping[key].left || 0, + right: this.offsetWidth + }; + } + }); + + // Treat fontSize special, inc/dec the width for testing purposes + acc[key]._styles.fontSize = styles.fontSize || '16px'; + /* eslint-disable object-shorthand */ + Object.defineProperty(acc[key].style, 'fontSize', { + get: function() { return elementMap[key]._styles.fontSize; }, + set: function(val) { + const curSize = parseFloat(elementMap[key]._styles.fontSize); + const newSize = parseFloat(val); + const adj = (newSize - curSize) / 10; + if (curSize < newSize) { + elementMap[key].offsetWidth += adj; + elementMap[key].clientWidth += adj; + elementMap[key].scrollWidth += adj; + } + if (curSize > newSize) { + elementMap[key].offsetWidth += adj; + elementMap[key].clientWidth += adj; + elementMap[key].scrollWidth += adj; + } + elementMap[key]._styles.fontSize = val; + }, + enumerable: true, + configurable: true + }); + return acc; + }, {}); + + Object.assign(elementMap, elements); +}; +utils.__setMockElements = __setMockElements; + +utils.__getMockElement = (key) => elementMap[key]; + +utils.setRef = (name, context) => () => { + context[name] = elementMap[name]; +}; +utils.setRef._isMock = true; + +utils.getStyle = (el, prop) => elementMap[el.className][utils.camelize(prop)]; + +const origGetOverflow = utils.getOverflow; +utils.getOverflow = (/* parent, child */) => { + const parent = elementMap.wrapper; + const child = elementMap.content; + return origGetOverflow(parent, child); +}; + +// const origHasOverflow = utils.hasOverflow; +utils.hasOverflow = (/* parent, child */) => { + const parent = elementMap.wrapper; + const child = elementMap.content; + if (child.style.position && child.style.position === 'absolute') { + return values(utils.getOverflow(parent, child)).some(v => v); + } + return (parent.clientWidth <= parent.scrollWidth || parent.clientHeight <= parent.scrollHeight); +}; + +module.exports = utils; diff --git a/src/__tests__/index.tests.js b/src/__tests__/index.tests.js index a05d0ef..2744054 100644 --- a/src/__tests__/index.tests.js +++ b/src/__tests__/index.tests.js @@ -1,7 +1,12 @@ +jest.setMock('../utils', require('../__mocks__/utils')); + import React from 'react'; -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import ScaleText from '../index'; +// jest.mock('../utils'); +const utils = require('utils'); + describe('ScaleText', () => { it('renders correctly', () => { @@ -13,26 +18,34 @@ describe('ScaleText', () => { // Element proeperties such as clientHeight/Width, scrollHeight/Width // getBoundingClientRect(), etc. jsdom doesn't polyfill those, as it // doesn't have a full rendering engine. - /* it('renders correctly, with minFontSize', () => { + it('renders correctly, with minFontSize', () => { + utils.__setMockElements({ + wrapper: { width: 100, height: 100, top: 0, left: 0 }, + content: { width: 105, height: 105, top: 0, left: 0 } + }); const pStyles = { width: '100px', height: '100px' }; const wrapper = mount(

Some text

); - console.log('offsetWidth: ', wrapper.find('.parent').offsetWidth); - const fontSize = parseFloat(wrapper.find('p').getDOMNode().style.fontSize, 10); - expect(fontSize).toBeGreaterThanOrEqual(12); + wrapper.update(); + const fontSize = utils.__getMockElement('content').style.fontSize; + expect(parseFloat(fontSize, 10)).toBeGreaterThanOrEqual(12); wrapper.unmount(); }); it('renders correctly, with maxFontSize', () => { + utils.__setMockElements({ + wrapper: { width: 800, height: 800, top: 0, left: 0 }, + content: { width: 100, height: 100, top: 0, left: 0 } + }); const pStyles = { width: '800px', height: '800px' }; const wrapper = mount(

Some text

); - const fontSize = parseFloat(wrapper.find('p').getDOMNode().style.fontSize, 10); - expect(fontSize).toBeLessThanOrEqual(20); + const fontSize = utils.__getMockElement('content').style.fontSize; + expect(parseFloat(fontSize, 10)).toBeLessThanOrEqual(20); wrapper.unmount(); - });*/ + }); }); diff --git a/src/__tests__/utils.tests.js b/src/__tests__/utils.tests.js index 9020971..8423ffa 100644 --- a/src/__tests__/utils.tests.js +++ b/src/__tests__/utils.tests.js @@ -1,27 +1,4 @@ -import { camelize, getStyle, getOverflow, hasOverflow } from '../utils'; - -function createMockDiv(width, height, styles = {}) { - const div = document.createElement('div'); - Object.assign(div.style, { - width: `${width}px`, - height: `${height}px`, - }, styles); - - // we have to mock this for jsdom. - div.getBoundingClientRect = () => ({ - width: div.style.width || width, - height: div.style.height || height, - top: 0, - left: 0, - right: div.style.width || width, - bottom: div.style.height || height, - }); - div.prototype.clientWidth = div.clientWidth || width; - div.prototype.scrollWidth = div.scrollWidth || width; - div.prototype.clientHeight = div.clientHeight || height; - div.prototype.scrollHeight = div.scrollHeight || height; - return div; -} +import { camelize, getStyle } from '../utils'; describe('camelize()', () => { it('correctly camelizes a dashed string', () => { @@ -47,24 +24,3 @@ describe('getStyle()', () => { }); }); -describe.skip('getOverflow() + hasOverflow()', () => { - // TODO: Find a way to mock clientHeight/Width and scrollHeight/Width - // so we can unit test these methods - it('detects and tests for overflow', () => { - document.body.innerHTML = ''; - - const parent = createMockDiv(100, 100, { overflow: 'hidden' }); - document.body.appendChild(parent); - const child = createMockDiv(200, 200); - parent.appendChild(child); - - expect(getOverflow(parent, child)).toMatchObject({ horizontal: true, vertical: true }); - expect(hasOverflow(parent, child)).toEqual(true); - child.style.height = '100px'; - expect(getOverflow(parent, child)).toMatchObject({ horizontal: true, vertical: false }); - expect(hasOverflow(parent, child)).toEqual(true); - child.style.width = '100px'; - expect(getOverflow(parent, child)).toMatchObject({ horizontal: false, vertical: false }); - expect(hasOverflow(parent, child)).toEqual(false); - }); -}); diff --git a/src/index.js b/src/index.js index 7450790..e0f35d7 100755 --- a/src/index.js +++ b/src/index.js @@ -1,19 +1,9 @@ import React, { Component, PropTypes } from 'react'; -import { hasOverflow } from './utils'; -// import debounce from 'lodash/debounce'; +import { setRef, hasOverflow } from './utils'; import throttle from 'lodash/throttle'; class ScaleText extends Component { - constructor(props) { - super(props); - this.state = { - wrapper: null, - content: null, - fontSize: null - }; - } - componentDidMount() { this.handleResize = throttle(this.scale.bind(this), 50, { leading: true }); window.addEventListener('resize', this.handleResize); @@ -61,8 +51,8 @@ class ScaleText extends Component { render() { const { children } = this.props; - const contentRef = (c) => { this.content = c; }; - const wrapperRef = (c) => { this.wrapper = c; }; + const contentRef = setRef('content', this); // (c) => { this.content = c; }; + const wrapperRef = setRef('wrapper', this); // (c) => { this.wrapper = c; }; const wrapStyle = { display: 'inline-block', overflow: 'hidden', @@ -71,7 +61,7 @@ class ScaleText extends Component { }; return ( -
+
{ React.Children.map(children, (child) => React.cloneElement(child, { ref: contentRef }) )[0] diff --git a/src/utils.js b/src/utils.js index 067f551..08433e1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -8,6 +8,11 @@ import values from 'lodash/values'; export const camelize = (str) => str.replace(/\-(\w)/g, (s, letter) => letter.toUpperCase()); +// Used to create ref setters so we can mock elements in tests +/* eslint-disable no-param-reassign */ +export const setRef = (name, context) => (e) => { context[name] = e; }; +/* eslint-enable no-param-reassign */ + // Get the current style property value for the given element export function getStyle(el, styleProp) { if (el.currentStyle) {