From 75c705009db282ff3e5324167caac6904abe0624 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Thu, 28 Oct 2021 09:04:43 +0100 Subject: [PATCH] [system] Improve breakpoints resolver function (#29300) --- packages/mui-lab/src/Masonry/Masonry.js | 44 +--- packages/mui-lab/src/Masonry/Masonry.test.js | 95 +++++++++ packages/mui-material/src/Grid/Grid.js | 32 ++- packages/mui-material/src/Grid/Grid.test.js | 188 ++++++++++++++---- packages/mui-material/src/Stack/Stack.js | 24 ++- packages/mui-material/src/Stack/Stack.test.js | 71 +++++++ packages/mui-system/src/breakpoints.js | 47 ++++- packages/mui-system/src/breakpoints.test.js | 186 ++++++++++++++++- 8 files changed, 585 insertions(+), 102 deletions(-) diff --git a/packages/mui-lab/src/Masonry/Masonry.js b/packages/mui-lab/src/Masonry/Masonry.js index 7a5b83acc46e8d..78c23bf31c44c4 100644 --- a/packages/mui-lab/src/Masonry/Masonry.js +++ b/packages/mui-lab/src/Masonry/Masonry.js @@ -22,40 +22,6 @@ const useUtilityClasses = (ownerState) => { return composeClasses(slots, getMasonryUtilityClass, classes); }; -// compute base for responsive values; e.g., -// [1,2,3] => {xs: true, sm: true, md: true} -// {xs: 1, sm: 2, md: 3} => {xs: true, sm: true, md: true} -const computeBreakpointsBase = (breakpoints, prop) => { - const base = {}; - if (Array.isArray(prop)) { - Object.keys(breakpoints.values).forEach((breakpoint, i, arr) => { - if (i < arr.length) { - base[breakpoint] = true; - } - }); - } else { - Object.keys(breakpoints.values).forEach((breakpoint) => { - if (prop[breakpoint] != null) { - base[breakpoint] = true; - } - }); - } - return base; -}; - -// if prop is an array, convert to object; e.g., -// (base: {xs: true, sm: true, md: true}, prop: [1,2,3]) => {xs: 1, sm: 2, md: 3} -const validatePropValues = (base, prop) => { - const values = {}; - if (Array.isArray(prop)) { - Object.keys(base).forEach((breakpoint, i) => { - values[breakpoint] = prop[i]; - }); - return values; - } - return prop; -}; - export const getStyle = ({ ownerState, theme }) => { let styles = { width: '100%', @@ -94,10 +60,9 @@ export const getStyle = ({ ownerState, theme }) => { }; } - const spacingBreakpointsBase = computeBreakpointsBase(theme.breakpoints, ownerState.spacing); const spacingValues = resolveBreakpointValues({ - values: validatePropValues(spacingBreakpointsBase, ownerState.spacing), - base: spacingBreakpointsBase, + values: ownerState.spacing, + breakpoints: theme.breakpoints.values, }); const transformer = createUnarySpacing(theme); @@ -120,10 +85,9 @@ export const getStyle = ({ ownerState, theme }) => { handleBreakpoints({ theme }, spacingValues, spacingStyleFromPropValue), ); - const columnBreakpointsBase = computeBreakpointsBase(theme.breakpoints, ownerState.columns); const columnValues = resolveBreakpointValues({ - values: validatePropValues(columnBreakpointsBase, ownerState.columns), - base: columnBreakpointsBase, + values: ownerState.columns, + breakpoints: theme.breakpoints.values, }); const columnStyleFromPropValue = (propValue) => { diff --git a/packages/mui-lab/src/Masonry/Masonry.test.js b/packages/mui-lab/src/Masonry/Masonry.test.js index 4b1a633edbb846..5f6e3b225a5c4c 100644 --- a/packages/mui-lab/src/Masonry/Masonry.test.js +++ b/packages/mui-lab/src/Masonry/Masonry.test.js @@ -213,4 +213,99 @@ describe('', () => { }); }); }); + + describe('prop: columns', () => { + it('should generate correct responsive styles regardless of breakpoints order', () => { + const columns = { sm: 5, md: 7, xs: 3 }; + const spacing = 1; + expect( + getStyle({ + ownerState: { + columns, + spacing, + maxColumnHeight, + }, + theme, + }), + ).to.deep.equal({ + width: '100%', + display: 'flex', + flexFlow: 'column wrap', + alignContent: 'space-between', + boxSizing: 'border-box', + '& > *': { + boxSizing: 'border-box', + margin: parseToNumber(theme.spacing(spacing)) / 2, + }, + margin: -(parseToNumber(theme.spacing(spacing)) / 2), + height: maxColumnHeight + parseToNumber(theme.spacing(spacing)), + [`@media (min-width:${theme.breakpoints.values.xs}px)`]: { + '& > *': { + width: `calc(${(100 / columns.xs).toFixed(2)}% - ${theme.spacing(spacing)})`, + }, + }, + [`@media (min-width:${theme.breakpoints.values.sm}px)`]: { + '& > *': { + width: `calc(${(100 / columns.sm).toFixed(2)}% - ${theme.spacing(spacing)})`, + }, + }, + [`@media (min-width:${theme.breakpoints.values.md}px)`]: { + '& > *': { + width: `calc(${(100 / columns.md).toFixed(2)}% - ${theme.spacing(spacing)})`, + }, + }, + }); + }); + }); + + describe('prop: spacing', () => { + it('should generate correct responsive styles regardless of breakpoints order', () => { + const columns = 4; + const spacing = { sm: 2, md: 3, xs: 1 }; + expect( + getStyle({ + ownerState: { + columns, + spacing, + maxColumnHeight, + }, + theme, + }), + ).to.deep.equal({ + width: '100%', + display: 'flex', + flexFlow: 'column wrap', + alignContent: 'space-between', + boxSizing: 'border-box', + '& > *': { + boxSizing: 'border-box', + width: `calc(${(100 / columns).toFixed(2)}% - 0px)`, + }, + [`@media (min-width:${theme.breakpoints.values.xs}px)`]: { + '& > *': { + margin: parseToNumber(theme.spacing(spacing.xs)) / 2, + width: `calc(${(100 / columns).toFixed(2)}% - ${theme.spacing(spacing.xs)})`, + }, + margin: -(parseToNumber(theme.spacing(spacing.xs)) / 2), + height: maxColumnHeight + parseToNumber(theme.spacing(spacing.xs)), + }, + [`@media (min-width:${theme.breakpoints.values.sm}px)`]: { + '& > *': { + margin: parseToNumber(theme.spacing(spacing.sm)) / 2, + width: `calc(${(100 / columns).toFixed(2)}% - ${theme.spacing(spacing.sm)})`, + }, + margin: -(parseToNumber(theme.spacing(spacing.sm)) / 2), + height: maxColumnHeight + parseToNumber(theme.spacing(spacing.sm)), + }, + [`@media (min-width:${theme.breakpoints.values.md}px)`]: { + '& > *': { + margin: parseToNumber(theme.spacing(spacing.md)) / 2, + width: `calc(${(100 / columns).toFixed(2)}% - ${theme.spacing(spacing.md)})`, + }, + margin: -(parseToNumber(theme.spacing(spacing.md)) / 2), + height: maxColumnHeight + parseToNumber(theme.spacing(spacing.md)), + }, + }); + }); + }); }); diff --git a/packages/mui-material/src/Grid/Grid.js b/packages/mui-material/src/Grid/Grid.js index f237ab63835638..acee341f052ee9 100644 --- a/packages/mui-material/src/Grid/Grid.js +++ b/packages/mui-material/src/Grid/Grid.js @@ -56,11 +56,15 @@ function generateGrid(globalStyles, theme, breakpoint, ownerState) { } else { const columnsBreakpointValues = resolveBreakpointValues({ values: ownerState.columns, - base: theme.breakpoints.values, + breakpoints: theme.breakpoints.values, }); + const columnValue = + typeof columnsBreakpointValues === 'object' + ? columnsBreakpointValues[breakpoint] + : columnsBreakpointValues; // Keep 7 significant numbers. - const width = `${Math.round((size / columnsBreakpointValues[breakpoint]) * 10e7) / 10e5}%`; + const width = `${Math.round((size / columnValue) * 10e7) / 10e5}%`; let more = {}; if (ownerState.container && ownerState.item && ownerState.columnSpacing !== 0) { @@ -92,8 +96,13 @@ function generateGrid(globalStyles, theme, breakpoint, ownerState) { } } -function generateDirection({ theme, ownerState }) { - return handleBreakpoints({ theme }, ownerState.direction, (propValue) => { +export function generateDirection({ theme, ownerState }) { + const directionValues = resolveBreakpointValues({ + values: ownerState.direction, + breakpoints: theme.breakpoints.values, + }); + + return handleBreakpoints({ theme }, directionValues, (propValue) => { const output = { flexDirection: propValue, }; @@ -113,7 +122,12 @@ export function generateRowGap({ theme, ownerState }) { let styles = {}; if (container && rowSpacing !== 0) { - styles = handleBreakpoints({ theme }, rowSpacing, (propValue) => { + const rowSpacingValues = resolveBreakpointValues({ + values: rowSpacing, + breakpoints: theme.breakpoints.values, + }); + + styles = handleBreakpoints({ theme }, rowSpacingValues, (propValue) => { const themeSpacing = theme.spacing(propValue); if (themeSpacing !== '0px') { @@ -137,9 +151,13 @@ export function generateColumnGap({ theme, ownerState }) { let styles = {}; if (container && columnSpacing !== 0) { - styles = handleBreakpoints({ theme }, columnSpacing, (propValue) => { - const themeSpacing = theme.spacing(propValue); + const columnSpacingValues = resolveBreakpointValues({ + values: columnSpacing, + breakpoints: theme.breakpoints.values, + }); + styles = handleBreakpoints({ theme }, columnSpacingValues, (propValue) => { + const themeSpacing = theme.spacing(propValue); if (themeSpacing !== '0px') { return { width: `calc(100% + ${getOffset(themeSpacing)})`, diff --git a/packages/mui-material/src/Grid/Grid.test.js b/packages/mui-material/src/Grid/Grid.test.js index 8919d59fb5dc68..0cf677aaba9fc7 100644 --- a/packages/mui-material/src/Grid/Grid.test.js +++ b/packages/mui-material/src/Grid/Grid.test.js @@ -4,7 +4,7 @@ import { describeConformance, createClientRender, screen } from 'test/utils'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import defaultTheme from '@mui/material/styles/defaultTheme'; import Grid, { gridClasses as classes } from '@mui/material/Grid'; -import { generateRowGap, generateColumnGap } from './Grid'; +import { generateRowGap, generateColumnGap, generateDirection } from './Grid'; describe('', () => { const render = createClientRender(); @@ -94,6 +94,59 @@ describe('', () => { }); }); + describe('prop: direction', () => { + it('should have a direction', () => { + const { container } = render(); + expect(container.firstChild).toHaveComputedStyle({ flexDirection: 'column' }); + }); + + it('should support responsive values', () => { + const theme = createTheme(); + expect( + generateDirection({ + ownerState: { + container: true, + direction: { xs: 'row', sm: 'column' }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + flexDirection: 'row', + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + flexDirection: 'column', + '& > .MuiGrid-item': { + maxWidth: 'none', + }, + }, + }); + }); + + it('should generate correct responsive styles regardless of breakpoints order', () => { + const theme = createTheme(); + expect( + generateDirection({ + ownerState: { + container: true, + direction: { sm: 'column', xs: 'row' }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + flexDirection: 'row', + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + flexDirection: 'column', + '& > .MuiGrid-item': { + maxWidth: 'none', + }, + }, + }); + }); + }); + describe('prop: spacing', () => { it('should have a spacing', () => { const { container } = render(); @@ -112,56 +165,107 @@ describe('', () => { paddingLeft: '12px', }); }); - }); - it('should generate responsive styles', () => { - const theme = createTheme(); - expect( - generateRowGap({ - ownerState: { - container: true, - rowSpacing: { xs: 1, sm: 2 }, + it('should generate correct responsive styles', () => { + const theme = createTheme(); + expect( + generateRowGap({ + ownerState: { + container: true, + rowSpacing: { xs: 1, sm: 2 }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + '& > .MuiGrid-item': { + paddingTop: '8px', + }, + marginTop: '-8px', + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + '& > .MuiGrid-item': { + paddingTop: '16px', + }, + marginTop: '-16px', }, - theme, - }), - ).to.deep.equal({ - '@media (min-width:0px)': { - '& > .MuiGrid-item': { - paddingTop: '8px', + }); + + expect( + generateColumnGap({ + ownerState: { + container: true, + columnSpacing: { xs: 1, sm: 2 }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + '& > .MuiGrid-item': { + paddingLeft: '8px', + }, + marginLeft: '-8px', + width: 'calc(100% + 8px)', }, - marginTop: '-8px', - }, - [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { - '& > .MuiGrid-item': { - paddingTop: '16px', + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + '& > .MuiGrid-item': { + paddingLeft: '16px', + }, + marginLeft: '-16px', + width: 'calc(100% + 16px)', }, - marginTop: '-16px', - }, + }); }); - expect( - generateColumnGap({ - ownerState: { - container: true, - columnSpacing: { xs: 1, sm: 2 }, + it('should generate correct responsive styles regardless of breakpoints order ', () => { + const theme = createTheme(); + expect( + generateRowGap({ + ownerState: { + container: true, + rowSpacing: { sm: 2, xs: 1 }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + '& > .MuiGrid-item': { + paddingTop: '8px', + }, + marginTop: '-8px', + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + '& > .MuiGrid-item': { + paddingTop: '16px', + }, + marginTop: '-16px', }, - theme, - }), - ).to.deep.equal({ - '@media (min-width:0px)': { - '& > .MuiGrid-item': { - paddingLeft: '8px', + }); + + expect( + generateColumnGap({ + ownerState: { + container: true, + columnSpacing: { sm: 2, xs: 1 }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + '& > .MuiGrid-item': { + paddingLeft: '8px', + }, + marginLeft: '-8px', + width: 'calc(100% + 8px)', }, - marginLeft: '-8px', - width: 'calc(100% + 8px)', - }, - [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { - '& > .MuiGrid-item': { - paddingLeft: '16px', + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + '& > .MuiGrid-item': { + paddingLeft: '16px', + }, + marginLeft: '-16px', + width: 'calc(100% + 16px)', }, - marginLeft: '-16px', - width: 'calc(100% + 16px)', - }, + }); }); }); diff --git a/packages/mui-material/src/Stack/Stack.js b/packages/mui-material/src/Stack/Stack.js index 22e7df4f7dfb45..bfa62f79935174 100644 --- a/packages/mui-material/src/Stack/Stack.js +++ b/packages/mui-material/src/Stack/Stack.js @@ -44,9 +44,16 @@ const getSideFromDirection = (direction) => { export const style = ({ ownerState, theme }) => { let styles = { display: 'flex', - ...handleBreakpoints({ theme }, ownerState.direction, (propValue) => ({ - flexDirection: propValue, - })), + ...handleBreakpoints( + { theme }, + resolveBreakpointValues({ + values: ownerState.direction, + breakpoints: theme.breakpoints.values, + }), + (propValue) => ({ + flexDirection: propValue, + }), + ), }; if (ownerState.spacing) { @@ -59,8 +66,15 @@ export const style = ({ ownerState, theme }) => { return acc; }, {}); - const directionValues = resolveBreakpointValues({ values: ownerState.direction, base }); - const spacingValues = resolveBreakpointValues({ values: ownerState.spacing, base }); + const directionValues = resolveBreakpointValues({ + values: ownerState.direction, + base, + }); + + const spacingValues = resolveBreakpointValues({ + values: ownerState.spacing, + base, + }); const styleFromPropValue = (propValue, breakpoint) => { return { diff --git a/packages/mui-material/src/Stack/Stack.test.js b/packages/mui-material/src/Stack/Stack.test.js index 64cfa47cb1474f..579980838e0c13 100644 --- a/packages/mui-material/src/Stack/Stack.test.js +++ b/packages/mui-material/src/Stack/Stack.test.js @@ -186,4 +186,75 @@ describe('', () => { display: 'flex', }); }); + + describe('prop: direction', () => { + it('should generate correct responsive styles regardless of breakpoints order', () => { + expect( + style({ + ownerState: { + direction: { sm: 'row', xs: 'column' }, + spacing: { xs: 1, sm: 2, md: 3 }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + '& > :not(style) + :not(style)': { + margin: 0, + marginTop: '8px', + }, + flexDirection: 'column', + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + '& > :not(style) + :not(style)': { + margin: 0, + marginLeft: '16px', + }, + flexDirection: 'row', + }, + [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: { + '& > :not(style) + :not(style)': { + margin: 0, + marginLeft: '24px', + }, + }, + display: 'flex', + }); + }); + }); + + describe('prop: spacing', () => { + it('should generate correct responsive styles regardless of breakpoints order', () => { + expect( + style({ + ownerState: { + direction: 'column', + spacing: { sm: 2, md: 3, xs: 1 }, + }, + theme, + }), + ).to.deep.equal({ + '@media (min-width:0px)': { + '& > :not(style) + :not(style)': { + margin: 0, + marginTop: '8px', + }, + }, + [`@media (min-width:${defaultTheme.breakpoints.values.sm}px)`]: { + '& > :not(style) + :not(style)': { + margin: 0, + marginTop: '16px', + }, + }, + [`@media (min-width:${defaultTheme.breakpoints.values.md}px)`]: { + '& > :not(style) + :not(style)': { + margin: 0, + marginTop: '24px', + }, + }, + display: 'flex', + flexDirection: 'column', + }); + }); + }); }); diff --git a/packages/mui-system/src/breakpoints.js b/packages/mui-system/src/breakpoints.js index edda30b4803fb6..fad8d2625e9762 100644 --- a/packages/mui-system/src/breakpoints.js +++ b/packages/mui-system/src/breakpoints.js @@ -113,7 +113,38 @@ export function mergeBreakpointsInOrder(breakpointsInput, ...styles) { return removeUnusedBreakpoints(Object.keys(emptyBreakpoints), mergedOutput); } -export function resolveBreakpointValues({ values: breakpointValues, base }) { +// compute base for responsive values; e.g., +// [1,2,3] => {xs: true, sm: true, md: true} +// {xs: 1, sm: 2, md: 3} => {xs: true, sm: true, md: true} +export function computeBreakpointsBase(breakpointValues, themeBreakpoints) { + // fixed value + if (typeof breakpointValues !== 'object') { + return {}; + } + const base = {}; + const breakpointsKeys = Object.keys(themeBreakpoints); + if (Array.isArray(breakpointValues)) { + breakpointsKeys.forEach((breakpoint, i) => { + if (i < breakpointValues.length) { + base[breakpoint] = true; + } + }); + } else { + breakpointsKeys.forEach((breakpoint) => { + if (breakpointValues[breakpoint] != null) { + base[breakpoint] = true; + } + }); + } + return base; +} + +export function resolveBreakpointValues({ + values: breakpointValues, + breakpoints: themeBreakpoints, + base: customBase, +}) { + const base = customBase || computeBreakpointsBase(breakpointValues, themeBreakpoints); const keys = Object.keys(base); if (keys.length === 0) { @@ -122,16 +153,18 @@ export function resolveBreakpointValues({ values: breakpointValues, base }) { let previous; - return keys.reduce((acc, breakpoint) => { - if (typeof breakpointValues === 'object') { + return keys.reduce((acc, breakpoint, i) => { + if (Array.isArray(breakpointValues)) { + acc[breakpoint] = + breakpointValues[i] != null ? breakpointValues[i] : breakpointValues[previous]; + previous = i; + } else { acc[breakpoint] = breakpointValues[breakpoint] != null ? breakpointValues[breakpoint] - : breakpointValues[previous]; - } else { - acc[breakpoint] = breakpointValues; + : breakpointValues[previous] || breakpointValues; + previous = breakpoint; } - previous = breakpoint; return acc; }, {}); } diff --git a/packages/mui-system/src/breakpoints.test.js b/packages/mui-system/src/breakpoints.test.js index dac47c5a994c15..0cb0b209bfa08f 100644 --- a/packages/mui-system/src/breakpoints.test.js +++ b/packages/mui-system/src/breakpoints.test.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import breakpoints from './breakpoints'; +import breakpoints, { computeBreakpointsBase, resolveBreakpointValues } from './breakpoints'; import style from './style'; const textColor = style({ @@ -8,6 +8,14 @@ const textColor = style({ }); describe('breakpoints', () => { + const muiThemeBreakpoints = { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 }; + const customThemeBreakpoints = { + extraSmall: 0, + small: 300, + medium: 600, + large: 900, + extraLarge: 1200, + }; it('should work', () => { const palette = breakpoints(textColor); @@ -26,4 +34,180 @@ describe('breakpoints', () => { }, }); }); + + describe('function: computeBreakpointsBase', () => { + describe('mui default breakpoints', () => { + it('compute base for breakpoint values of array type', () => { + const columns = [1, 2, 3]; + const base = computeBreakpointsBase(columns, muiThemeBreakpoints); + expect(base).to.deep.equal({ xs: true, sm: true, md: true }); + }); + + it('compute base for breakpoint values of object type', () => { + const columns = { xs: 1, sm: 2, md: 3 }; + const base = computeBreakpointsBase(columns, muiThemeBreakpoints); + expect(base).to.deep.equal({ xs: true, sm: true, md: true }); + }); + + it('return empty object for fixed value', () => { + const columns = 3; + const base = computeBreakpointsBase(columns, muiThemeBreakpoints); + expect(base).to.deep.equal({}); + }); + }); + + describe('custom breakpoints', () => { + it('compute base for breakpoint values of array type', () => { + const columns = [1, 2, 3]; + const base = computeBreakpointsBase(columns, customThemeBreakpoints); + expect(base).to.deep.equal({ extraSmall: true, small: true, medium: true }); + }); + + it('compute base for breakpoint values of object type', () => { + const columns = { extraSmall: 1, small: 2, medium: 3 }; + const base = computeBreakpointsBase(columns, customThemeBreakpoints); + expect(base).to.deep.equal({ extraSmall: true, small: true, medium: true }); + }); + + it('return empty object for fixed value', () => { + const columns = 3; + const base = computeBreakpointsBase(columns, customThemeBreakpoints); + expect(base).to.deep.equal({}); + }); + }); + }); + + describe('function: resolveBreakpointValues', () => { + describe('mui default breakpoints', () => { + it('resolve breakpoint values for prop of array type', () => { + const columns = [1, 2, 3]; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: muiThemeBreakpoints, + }); + expect(values).to.deep.equal({ xs: 1, sm: 2, md: 3 }); + }); + + it('resolve breakpoint values for prop of object type', () => { + const columns = { xs: 1, sm: 2, md: 3 }; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: muiThemeBreakpoints, + }); + expect(values).to.deep.equal({ xs: 1, sm: 2, md: 3 }); + }); + + it('resolve breakpoint values for unordered prop of object type', () => { + const columns = { sm: 2, md: 3, xs: 1 }; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: muiThemeBreakpoints, + }); + expect(values).to.deep.equal({ xs: 1, sm: 2, md: 3 }); + }); + + it('return prop as it is for prop of fixed value', () => { + const columns = 3; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: muiThemeBreakpoints, + }); + expect(values).to.equal(3); + }); + + it('given custom base, resolve breakpoint values for prop of array type', () => { + const columns = [1, 2, 3]; + const customBase = { xs: true, sm: true, md: true, lg: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ xs: 1, sm: 2, md: 3, lg: 3 }); + }); + + it('given custom base, resolve breakpoint values for prop of object type', () => { + const columns = { xs: 1, sm: 2, md: 3 }; + const customBase = { xs: true, sm: true, md: true, lg: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ xs: 1, sm: 2, md: 3, lg: 3 }); + }); + + it('given custom base, resolve breakpoint values for unordered prop of object type', () => { + const columns = { sm: 2, md: 3, xs: 1 }; + const customBase = { xs: true, sm: true, md: true, lg: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ xs: 1, sm: 2, md: 3, lg: 3 }); + }); + + it('given custom base, resolve breakpoint values for prop of object type with missing breakpoints', () => { + const columns = { xs: 1, md: 2 }; + const customBase = { xs: true, sm: true, md: true, lg: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ xs: 1, sm: 1, md: 2, lg: 2 }); + }); + + it('given custom base, resolve breakpoint values for unordered prop of object type with missing breakpoints', () => { + const columns = { md: 2, xs: 1 }; + const customBase = { xs: true, sm: true, md: true, lg: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ xs: 1, sm: 1, md: 2, lg: 2 }); + }); + }); + + describe('custom breakpoints', () => { + it('resolve breakpoint values for prop of array type', () => { + const columns = [1, 2, 3]; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: customThemeBreakpoints, + }); + expect(values).to.deep.equal({ extraSmall: 1, small: 2, medium: 3 }); + }); + + it('resolve breakpoint values for prop of object type', () => { + const columns = { extraSmall: 1, small: 2, medium: 3 }; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: customThemeBreakpoints, + }); + expect(values).to.deep.equal({ extraSmall: 1, small: 2, medium: 3 }); + }); + + it('resolve breakpoint values for unordered prop of object type', () => { + const columns = { small: 2, medium: 3, extraSmall: 1 }; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: customThemeBreakpoints, + }); + expect(values).to.deep.equal({ extraSmall: 1, small: 2, medium: 3 }); + }); + + it('return prop as it is for prop of fixed value', () => { + const columns = 3; + const values = resolveBreakpointValues({ + values: columns, + breakpoints: customThemeBreakpoints, + }); + expect(values).to.equal(3); + }); + + it('given custom base, resolve breakpoint values for prop of array type', () => { + const columns = [1, 2, 3]; + const customBase = { extraSmall: true, small: true, medium: true, large: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ extraSmall: 1, small: 2, medium: 3, large: 3 }); + }); + + it('given custom base, resolve breakpoint values for prop of object type', () => { + const columns = { extraSmall: 1, small: 2, medium: 3 }; + const customBase = { extraSmall: true, small: true, medium: true, large: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ extraSmall: 1, small: 2, medium: 3, large: 3 }); + }); + + it('given custom base, resolve breakpoint values for unordered prop of object type', () => { + const columns = { small: 2, medium: 3, extraSmall: 1 }; + const customBase = { extraSmall: true, small: true, medium: true, large: true }; + const values = resolveBreakpointValues({ values: columns, base: customBase }); + expect(values).to.deep.equal({ extraSmall: 1, small: 2, medium: 3, large: 3 }); + }); + }); + }); });