Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge styles into single object #473

Merged
merged 5 commits into from May 4, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -58,6 +58,11 @@
"bench",
"examples",
"packages/**/*"
],
"reporter": [
"lcov",
"text-summary",
"html"
]
},
"bundlesize": [
Expand Down
22 changes: 20 additions & 2 deletions src/index.js
Expand Up @@ -35,6 +35,24 @@ export const createMediaQuery = n => `@media screen and (min-width: ${px(n)})`

const getValue = (n, scale) => get(scale, n)

// loosely based on deepmerge package
export const merge = (a, b) => {
const result = {}
Object.keys(a).forEach(key => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth doing micro perf optimizations here by using a for loop instead of .keys, .forEach, .reduce, since this is something that will be done a lot.

result[key] = a[key]
})
Object.keys(b).forEach(key => {
if (!a[key]) {
result[key] = b[key]
} else {
result[key] = merge(a[key], b[key])
}
})
return result
}

const mergeAll = (...args) => args.reduce((acc, arg) => merge(acc, arg), {})

export const style = ({
prop,
cssProperty,
Expand Down Expand Up @@ -83,7 +101,7 @@ export const style = ({
styles.sort()
}

return styles
return mergeAll(...styles)
}

func.propTypes = {
Expand All @@ -108,7 +126,7 @@ export const style = ({
export const compose = (...funcs) => {
const func = props => {
const n = funcs.map(fn => fn(props)).filter(Boolean)
return n
return mergeAll(...n)
}

func.propTypes = {}
Expand Down
54 changes: 34 additions & 20 deletions test/index.js
Expand Up @@ -10,6 +10,7 @@ import {
variant,
cloneFunction,
mapProps,
merge,
} from '../src'

const width = style({
Expand Down Expand Up @@ -88,32 +89,32 @@ test('returns an array of responsive style objects', t => {
const style = width({
width: ['100%', '50%'],
})
t.deepEqual(style, [
{ width: '100%' },
{ '@media screen and (min-width: 40em)': { width: '50%' } },
])
t.deepEqual(style, {
width: '100%',
'@media screen and (min-width: 40em)': { width: '50%' },
})
})

test('returns an array of responsive style objects for all breakpoints', t => {
const style = width({
width: ['100%', '75%', '50%', '33%', '25%'],
})
t.deepEqual(style, [
{ width: '100%' },
{ '@media screen and (min-width: 40em)': { width: '75%' } },
{ '@media screen and (min-width: 52em)': { width: '50%' } },
{ '@media screen and (min-width: 64em)': { width: '33%' } },
])
t.deepEqual(style, {
width: '100%',
'@media screen and (min-width: 40em)': { width: '75%' },
'@media screen and (min-width: 52em)': { width: '50%' },
'@media screen and (min-width: 64em)': { width: '33%' },
})
})

test('skips undefined responsive values', t => {
const style = width({
width: ['100%', , '50%'],
})
t.deepEqual(style, [
{ width: '100%' },
{ '@media screen and (min-width: 52em)': { width: '50%' } },
])
t.deepEqual(style, {
width: '100%',
'@media screen and (min-width: 52em)': { width: '50%' },
})
})

test('parses object values', t => {
Expand All @@ -123,10 +124,10 @@ test('parses object values', t => {
2: '50%',
},
})
t.deepEqual(style, [
{ width: '100%' },
{ '@media screen and (min-width: 64em)': { width: '50%' } },
])
t.deepEqual(style, {
width: '100%',
'@media screen and (min-width: 64em)': { width: '50%' },
})
})

test('get returns a value', t => {
Expand Down Expand Up @@ -191,7 +192,7 @@ test('compose combines style functions', t => {
bg: 'black',
})
t.is(typeof colors, 'function')
t.deepEqual(styles, [{ color: 'tomato' }, { backgroundColor: 'black' }])
t.deepEqual(styles, { color: 'tomato', backgroundColor: 'black' })
})

test('num returns true for numbers', t => {
Expand Down Expand Up @@ -265,11 +266,24 @@ test('array values longer than breakpoints does not reset returned style object'
const a = width({
width: ['100%', , , , , '50%', '25%'],
})
t.deepEqual(a, [{ width: '100%' }])
t.deepEqual(a, { width: '100%' })
})

test('mapProps copies propTypes', t => {
const margin = style({ prop: 'margin' })
const func = mapProps(props => props)(margin)
t.is(typeof func.propTypes, 'object')
})

test('merge deeply merges', t => {
const result = merge(
{ hello: { hi: 'beep' } },
{ hello: { hey: 'boop' } },
)
t.deepEqual(result, {
hello: {
hi: 'beep',
hey: 'boop',
}
})
})
66 changes: 32 additions & 34 deletions test/styles.js
Expand Up @@ -22,7 +22,7 @@ const theme = {

test('returns color values from theme', t => {
const a = color({ theme, color: 'blue', bg: 'black' })
t.deepEqual(a, [{ color: '#07c' }, { backgroundColor: '#111' }])
t.deepEqual(a, { color: '#07c', backgroundColor: '#111' })
})

test('returns raw color values', t => {
Expand All @@ -31,15 +31,15 @@ test('returns raw color values', t => {
color: 'inherit',
bg: 'tomato',
})
t.deepEqual(a, [{ color: 'inherit' }, { backgroundColor: 'tomato' }])
t.deepEqual(a, { color: 'inherit', backgroundColor: 'tomato' })
})

test('backgroundColor prop overrides bg prop', t => {
const a = color({
backgroundColor: 'tomato',
bg: 'blue',
})
t.deepEqual(a, [{ backgroundColor: 'tomato' }])
t.deepEqual(a, { backgroundColor: 'tomato' })
})

test('returns a pixel font-size', t => {
Expand Down Expand Up @@ -76,22 +76,22 @@ test('returns an array of style objects', t => {
const styles = space({
m: '4px',
})
t.deepEqual(styles, [{ margin: '4px' }])
t.deepEqual(styles, { margin: '4px' })
})

test('returns 0 values', t => {
const styles = space({ m: 0 })
t.deepEqual(styles, [{ margin: 0 }])
t.deepEqual(styles, { margin: 0 })
})

test('returns negative pixel values', t => {
const styles = space({ m: -2 })
t.deepEqual(styles, [{ margin: '-8px' }])
t.deepEqual(styles, { margin: '-8px' })
})

test('returns negative em values', t => {
const styles = space({ m: '-16em' })
t.deepEqual(styles, [{ margin: '-16em' }])
t.deepEqual(styles, { margin: '-16em' })
})

test('returns negative theme values', t => {
Expand All @@ -101,7 +101,7 @@ test('returns negative theme values', t => {
},
m: -2,
})
t.deepEqual(styles, [{ margin: '-8px' }])
t.deepEqual(styles, { margin: '-8px' })
})

test('returns positive theme values', t => {
Expand All @@ -111,27 +111,25 @@ test('returns positive theme values', t => {
},
m: 2,
})
t.deepEqual(styles, [{ margin: '2em' }])
t.deepEqual(styles, { margin: '2em' })
})

test('returns responsive values', t => {
const styles = space({
m: [0, 2, 3],
})
t.deepEqual(styles, [
[
{ margin: 0 },
{ '@media screen and (min-width: 40em)': { margin: '8px' } },
{ '@media screen and (min-width: 52em)': { margin: '16px' } },
],
])
t.deepEqual(styles, {
margin: 0,
'@media screen and (min-width: 40em)': { margin: '8px' },
'@media screen and (min-width: 52em)': { margin: '16px' },
})
})

test('returns aliased values', t => {
const styles = space({
px: 2,
})
t.deepEqual(styles, [{ paddingLeft: '8px' }, { paddingRight: '8px' }])
t.deepEqual(styles, { paddingLeft: '8px', paddingRight: '8px' })
})

test('returns string values from theme', t => {
Expand All @@ -141,7 +139,7 @@ test('returns string values from theme', t => {
},
padding: 1,
})
t.deepEqual(styles, [{ padding: '1em' }])
t.deepEqual(styles, { padding: '1em' })
})

test('returns negative string values from theme', t => {
Expand All @@ -151,7 +149,7 @@ test('returns negative string values from theme', t => {
},
margin: -1,
})
t.deepEqual(styles, [{ margin: '-1em' }])
t.deepEqual(styles, { margin: '-1em' })
})

test('returns values from theme object', t => {
Expand All @@ -162,57 +160,57 @@ test('returns values from theme object', t => {
margin: 'sm',
})

t.deepEqual(styles, [{ margin: '1px' }])
t.deepEqual(styles, { margin: '1px' })
})

test('pl prop sets paddingLeft', t => {
const styles = space({ pl: 2 })
t.deepEqual(styles, [{ paddingLeft: '8px' }])
t.deepEqual(styles, { paddingLeft: '8px' })
})

test('pl prop sets paddingLeft 0', t => {
const styles = space({ pl: 0 })
t.deepEqual(styles, [{ paddingLeft: 0 }])
t.deepEqual(styles, { paddingLeft: 0 })
})

test('px prop overrides pl prop', t => {
const styles = space({
pl: 1,
px: 2,
})
t.deepEqual(styles, [{ paddingLeft: '8px' }, { paddingRight: '8px' }])
t.deepEqual(styles, { paddingLeft: '8px', paddingRight: '8px' })
})

test('py prop overrides pb prop', t => {
const styles = space({
pb: 1,
py: 2,
})
t.deepEqual(styles, [{ paddingTop: '8px' }, { paddingBottom: '8px' }])
t.deepEqual(styles, { paddingTop: '8px', paddingBottom: '8px' })
})

test('mx prop overrides mr prop', t => {
const styles = space({
mr: 1,
mx: 2,
})
t.deepEqual(styles, [{ marginLeft: '8px' }, { marginRight: '8px' }])
t.deepEqual(styles, { marginLeft: '8px', marginRight: '8px' })
})

test('my prop overrides mt prop', t => {
const styles = space({
mt: 1,
my: 2,
})
t.deepEqual(styles, [{ marginTop: '8px' }, { marginBottom: '8px' }])
t.deepEqual(styles, { marginTop: '8px', marginBottom: '8px' })
})

test('margin overrides m prop', t => {
const styles = space({
m: 1,
margin: 2,
})
t.deepEqual(styles, [{ margin: '8px' }])
t.deepEqual(styles, { margin: '8px' })
})

test('space includes propTypes', t => {
Expand All @@ -225,7 +223,7 @@ test('size returns width and height', t => {
const styles = size({
size: 4,
})
t.deepEqual(styles, [{ width: '4px' }, { height: '4px' }])
t.deepEqual(styles, { width: '4px', height: '4px' })
})

// grid
Expand Down Expand Up @@ -332,10 +330,10 @@ test('borders prop returns correct sequence', t => {
borderStyle: 'dashed',
borderColor: 'red',
})
t.deepEqual(a, [
{ borderBottom: '1px solid' },
{ borderWidth: '2px' },
{ borderStyle: 'dashed' },
{ borderColor: 'red' },
])
t.deepEqual(a, {
borderBottom: '1px solid',
borderWidth: '2px',
borderStyle: 'dashed',
borderColor: 'red',
})
})