├── .eslintignore ├── .npmrc ├── src ├── index.js ├── __tests__ │ ├── temp.js │ ├── testHelpers │ │ ├── runTests.js │ │ ├── cssSerialiser.js │ │ ├── matchers │ │ │ └── customMatchers.js │ │ ├── toCSS.js │ │ ├── testRangedFeatureHelpers.js │ │ ├── testRangedFeature.js │ │ ├── featureValues.js │ │ └── data.js │ ├── __snapshots__ │ │ ├── rangedQueries.js.snap │ │ ├── configuration.js.snap │ │ ├── linearFeatures.js.snap │ │ ├── rangedFeatures.js.snap │ │ └── rangedFeatureHelpers.js.snap │ ├── rangedQueries.js │ ├── api │ │ ├── __snapshots__ │ │ │ ├── tweakpoints.js.snap │ │ │ ├── mediaType.js.snap │ │ │ └── query.js.snap │ │ ├── mediaType.js │ │ ├── tweakpoints.js │ │ └── query.js │ ├── utils │ │ └── value.js │ ├── sharedTests │ │ ├── config.js │ │ ├── rangedFeatureHelpers.js │ │ └── features.js │ ├── validations │ │ ├── transformers │ │ │ ├── resolutionTransformer.js │ │ │ └── dimensionTransformer.js │ │ └── validators │ │ │ └── validateIsNegationObject.js │ ├── rangedFeatureHelpers.js │ ├── linearFeatures.js │ ├── rangedFeatures.js │ └── configuration.js ├── validations │ ├── transformers │ │ ├── identityTransformer.js │ │ ├── numberOrPxNumberToNumber.js │ │ ├── resolutionTransformer.js │ │ └── dimensionTransformer.js │ ├── validators │ │ ├── args │ │ │ ├── validateAPINot.js │ │ │ ├── validateMQArgs.js │ │ │ ├── validateAPITweak.js │ │ │ ├── validateAPIQuery.js │ │ │ └── validateAPIMediaTypeArgs.js │ │ ├── validateBreakpoints.js │ │ ├── validateIsDPI.js │ │ ├── validateIsEm.js │ │ ├── validateIsPositiveValidNumber.js │ │ ├── validateIsPx.js │ │ ├── validateIsRem.js │ │ ├── validateIsNonNegativeValidNumber.js │ │ ├── validateBreakpointMapNames.js │ │ ├── validateIsValidDimensionsUnit.js │ │ ├── validateIsNumberOrPx.js │ │ ├── validateIsResolution.js │ │ ├── validateIsAspectRatio.js │ │ ├── validateIsNonNegativeValidInteger.js │ │ ├── validateIsValidMediaType.js │ │ ├── validateIsNegationObject.js │ │ ├── validateIsDimension.js │ │ ├── validateIsNegationElement.js │ │ └── validateIsQueryElement.js │ ├── validatorUIDs.js │ ├── validatorForLinearFeature.js │ └── validatorMessages.js ├── utils │ ├── query.js │ ├── validations.js │ ├── units.js │ ├── object.js │ ├── features.js │ ├── string.js │ ├── breakpoints.js │ └── predicates.js ├── renderers │ └── cssRenderers │ │ ├── styledComponentsRenderer.js │ │ └── queryRenderer.js ├── api │ ├── untweaked.js │ ├── not.js │ ├── tweak.js │ ├── mediaType.js │ └── query.js ├── features │ ├── buildLinearFeatures.js │ ├── buildRangedQueries.js │ ├── buildLinearFeature.js │ ├── buildRangedFeatures.js │ └── buildRangedFeature.js ├── mq.js ├── api.js ├── errors.js ├── const.js ├── features.js ├── errors2.js └── constraints.js ├── .gitignore ├── .prettierignore ├── docs └── images │ ├── atWidth.png │ ├── aboveWidth.png │ ├── belowWidth.png │ ├── betweenWidths.png │ ├── styled-mq-logo.png │ └── atBreakpointWidth.png ├── .stylelintrc ├── .babelrc ├── .prettierrc ├── .travis.yml ├── jest.config.js ├── LICENSE.md ├── .eslintrc.js ├── package.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | unsafe-perm = true -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import mq from './mq' 2 | 3 | export default mq 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | coverage 4 | out 5 | lib 6 | TODO.md -------------------------------------------------------------------------------- /src/__tests__/temp.js: -------------------------------------------------------------------------------- 1 | it(`temp`, () => { 2 | expect(true).toEqual(true) 3 | }) 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /Users/pedr/Library/Application\ Support/Code/User/snippets/javascript.json -------------------------------------------------------------------------------- /docs/images/atWidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Undistraction/cssapi-mq/HEAD/docs/images/atWidth.png -------------------------------------------------------------------------------- /docs/images/aboveWidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Undistraction/cssapi-mq/HEAD/docs/images/aboveWidth.png -------------------------------------------------------------------------------- /docs/images/belowWidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Undistraction/cssapi-mq/HEAD/docs/images/belowWidth.png -------------------------------------------------------------------------------- /docs/images/betweenWidths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Undistraction/cssapi-mq/HEAD/docs/images/betweenWidths.png -------------------------------------------------------------------------------- /docs/images/styled-mq-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Undistraction/cssapi-mq/HEAD/docs/images/styled-mq-logo.png -------------------------------------------------------------------------------- /docs/images/atBreakpointWidth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Undistraction/cssapi-mq/HEAD/docs/images/atBreakpointWidth.png -------------------------------------------------------------------------------- /src/validations/transformers/identityTransformer.js: -------------------------------------------------------------------------------- 1 | import { identity } from 'ramda' 2 | 3 | export default () => identity 4 | -------------------------------------------------------------------------------- /src/utils/query.js: -------------------------------------------------------------------------------- 1 | import { join } from 'ramda' 2 | 3 | // eslint-disable-next-line import/prefer-default-export 4 | export const joinWithAnd = join(` and `) 5 | -------------------------------------------------------------------------------- /src/utils/validations.js: -------------------------------------------------------------------------------- 1 | import { prop } from 'ramda' 2 | 3 | // eslint-disable-next-line import/prefer-default-export 4 | export const propValue = prop(`value`) 5 | -------------------------------------------------------------------------------- /src/renderers/cssRenderers/styledComponentsRenderer.js: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components' 2 | 3 | export default (definition, content) => css` 4 | ${definition} { 5 | ${content}; 6 | } 7 | ` 8 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/runTests.js: -------------------------------------------------------------------------------- 1 | import { map } from 'ramda' 2 | 3 | export default (tests, name, camelisedName, method, config) => { 4 | map(test => test(name, camelisedName, method, config))(tests) 5 | } 6 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-styled-components"], 3 | "extends": [ 4 | "stylelint-config-standard", 5 | "stylelint-config-styled-components" 6 | ], 7 | "syntax": "scss" 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/units.js: -------------------------------------------------------------------------------- 1 | import { SEPARATOR_VALUES } from '../const' 2 | import { propOf } from './object' 3 | 4 | // eslint-disable-next-line import/prefer-default-export 5 | export const separatorValueForUnit = propOf(SEPARATOR_VALUES) 6 | -------------------------------------------------------------------------------- /src/validations/validators/args/validateAPINot.js: -------------------------------------------------------------------------------- 1 | import { validateObjectWithConstraints } from 'folktale-validations' 2 | import { API_NOT } from '../../../constraints' 3 | 4 | export default validateObjectWithConstraints(API_NOT) 5 | -------------------------------------------------------------------------------- /src/validations/validators/args/validateMQArgs.js: -------------------------------------------------------------------------------- 1 | import { validateObjectWithConstraints } from 'folktale-validations' 2 | import { MQ_ARGS } from '../../../constraints' 3 | 4 | export default validateObjectWithConstraints(MQ_ARGS) 5 | -------------------------------------------------------------------------------- /src/utils/object.js: -------------------------------------------------------------------------------- 1 | import { compose, curry, reduce, toPairs, flip, prop } from 'ramda' 2 | 3 | export const reduceObjIndexed = curry((f, acc, v) => 4 | compose(reduce(f, acc), toPairs)(v) 5 | ) 6 | 7 | export const propOf = flip(prop) 8 | -------------------------------------------------------------------------------- /src/validations/validators/args/validateAPITweak.js: -------------------------------------------------------------------------------- 1 | import { validateObjectWithConstraints } from 'folktale-validations' 2 | import { API_TWEAK } from '../../../constraints' 3 | 4 | export default validateObjectWithConstraints(API_TWEAK) 5 | -------------------------------------------------------------------------------- /src/validations/validators/validateBreakpoints.js: -------------------------------------------------------------------------------- 1 | import { validateObjectWithConstraints } from 'folktale-validations' 2 | import { BREAKPOINTS } from '../../constraints' 3 | 4 | export default validateObjectWithConstraints(BREAKPOINTS) 5 | -------------------------------------------------------------------------------- /src/validations/validators/args/validateAPIQuery.js: -------------------------------------------------------------------------------- 1 | import { validateObjectWithConstraints } from 'folktale-validations' 2 | import { API_QUERY } from '../../../constraints' 3 | 4 | export default v => validateObjectWithConstraints(API_QUERY)(v) 5 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsDPI.js: -------------------------------------------------------------------------------- 1 | import { validateIsValidPositiveNumberWithUnit } from 'folktale-validations' 2 | import { RESOLUTION_UNIT } from '../../const' 3 | 4 | export default validateIsValidPositiveNumberWithUnit(RESOLUTION_UNIT) 5 | -------------------------------------------------------------------------------- /src/validations/validators/args/validateAPIMediaTypeArgs.js: -------------------------------------------------------------------------------- 1 | import { validateObjectWithConstraints } from 'folktale-validations' 2 | import { API_MEDIA_TYPE } from '../../../constraints' 3 | 4 | export default validateObjectWithConstraints(API_MEDIA_TYPE) 5 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsEm.js: -------------------------------------------------------------------------------- 1 | import { validateIsValidNonNegativeNumberWithUnit } from 'folktale-validations' 2 | import { DIMENSIONS_UNITS } from '../../const' 3 | 4 | export default validateIsValidNonNegativeNumberWithUnit(DIMENSIONS_UNITS.EM) 5 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsPositiveValidNumber.js: -------------------------------------------------------------------------------- 1 | import { 2 | andValidator, 3 | validateIsValidNumber, 4 | validateIsPositive, 5 | } from 'folktale-validations' 6 | 7 | export default andValidator(validateIsValidNumber, validateIsPositive) 8 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsPx.js: -------------------------------------------------------------------------------- 1 | import { validateIsValidNonNegativeNumberWithUnit } from 'folktale-validations' 2 | import { DIMENSIONS_UNITS } from '../../const' 3 | 4 | export default validateIsValidNonNegativeNumberWithUnit(DIMENSIONS_UNITS.PX) 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["transform-es2015-destructuring", "transform-object-rest-spread"], 3 | "env": { 4 | "test": { 5 | "presets": [["env"]] 6 | }, 7 | "development": { 8 | "presets": [["env"]] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/validations/transformers/numberOrPxNumberToNumber.js: -------------------------------------------------------------------------------- 1 | import { numericPartOfUnitedNumber } from 'cssapi-units' 2 | import { isNumber } from 'ramda-adjunct' 3 | import { unless } from 'ramda' 4 | 5 | export default unless(isNumber, numericPartOfUnitedNumber) 6 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsRem.js: -------------------------------------------------------------------------------- 1 | import { validateIsValidNonNegativeNumberWithUnit } from 'folktale-validations' 2 | import { DIMENSIONS_UNITS } from '../../const' 3 | 4 | export default validateIsValidNonNegativeNumberWithUnit(DIMENSIONS_UNITS.REM) 5 | -------------------------------------------------------------------------------- /src/api/untweaked.js: -------------------------------------------------------------------------------- 1 | import { when } from 'ramda' 2 | import { isUndefined } from 'ramda-adjunct' 3 | import { throwAPIUntweakedError } from '../errors2' 4 | 5 | export default originalMQ => () => 6 | when(isUndefined, () => throwAPIUntweakedError())(originalMQ) 7 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsNonNegativeValidNumber.js: -------------------------------------------------------------------------------- 1 | import { 2 | andValidator, 3 | validateIsValidNumber, 4 | validateIsNonNegative, 5 | } from 'folktale-validations' 6 | 7 | export default andValidator(validateIsValidNumber, validateIsNonNegative) 8 | -------------------------------------------------------------------------------- /src/validations/validatorUIDs.js: -------------------------------------------------------------------------------- 1 | export default { 2 | VALIDATE_IS_ASPECT_RATIO_STRING: `cssapi-mq.validateIsAspectRatioString`, 3 | VALIDATE_IS_NON_NEGATIVE: `cssapi-mq.validateIsNonNegative`, 4 | VALIDATE_IS_NEGATION_OBJECT: `cssapi-mq.validateIsNegationObject`, 5 | } 6 | -------------------------------------------------------------------------------- /src/validations/validators/validateBreakpointMapNames.js: -------------------------------------------------------------------------------- 1 | import { validateWhitelistedKeys } from 'folktale-validations' 2 | import { values } from 'ramda' 3 | import { BREAKPOINT_MAP_NAMES } from '../../const' 4 | 5 | export default validateWhitelistedKeys(values(BREAKPOINT_MAP_NAMES)) 6 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsValidDimensionsUnit.js: -------------------------------------------------------------------------------- 1 | import { values } from 'ramda' 2 | import { validateIsWhitelistedValue } from 'folktale-validations' 3 | import { DIMENSIONS_UNITS } from '../../const' 4 | 5 | export default validateIsWhitelistedValue(values(DIMENSIONS_UNITS)) 6 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/rangedQueries.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`rangedQueries generates ranged queries 1`] = ` 4 | Array [ 5 | " 6 | ", 7 | "@media (min-width: 56.25em)", 8 | " { 9 | ", 10 | "; 11 | } 12 | ", 13 | ] 14 | `; 15 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/cssSerialiser.js: -------------------------------------------------------------------------------- 1 | import { T } from 'ramda' 2 | import toCSS from './toCSS' 3 | 4 | export default { 5 | test: T, 6 | // TODO would be nice to use the default serializer if val is an error 7 | // string so it isn't formatted by toCSS. 8 | print: toCSS, 9 | } 10 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsNumberOrPx.js: -------------------------------------------------------------------------------- 1 | import { orValidator } from 'folktale-validations' 2 | import validateIsPx from './validateIsPx' 3 | import validateIsNonNegativeValidNumber from './validateIsNonNegativeValidNumber' 4 | 5 | export default orValidator(validateIsNonNegativeValidNumber, validateIsPx) 6 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsResolution.js: -------------------------------------------------------------------------------- 1 | import { anyOfValidator } from 'folktale-validations' 2 | import validateIsDPI from './validateIsDPI' 3 | import validateIsPositiveValidNumber from './validateIsPositiveValidNumber' 4 | 5 | export default anyOfValidator([validateIsPositiveValidNumber, validateIsDPI]) 6 | -------------------------------------------------------------------------------- /src/validations/validatorForLinearFeature.js: -------------------------------------------------------------------------------- 1 | import { compose } from 'ramda' 2 | import { validateObjectWithConstraints } from 'folktale-validations' 3 | import { constraintsForLinearFeature } from '../constraints' 4 | 5 | export default compose( 6 | validateObjectWithConstraints, 7 | constraintsForLinearFeature 8 | ) 9 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsAspectRatio.js: -------------------------------------------------------------------------------- 1 | import { predicateValidator } from 'folktale-validations' 2 | import { isAspectRatioString } from 'cssjs-units' 3 | import validatorUIDs from '../validatorUIDs' 4 | 5 | export default predicateValidator( 6 | isAspectRatioString, 7 | validatorUIDs.VALIDATE_IS_ASPECT_RATIO_STRING 8 | ) 9 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsNonNegativeValidInteger.js: -------------------------------------------------------------------------------- 1 | import { 2 | allOfValidator, 3 | validateIsValidNumber, 4 | validateIsInteger, 5 | validateIsNonNegative, 6 | } from 'folktale-validations' 7 | 8 | export default allOfValidator([ 9 | validateIsValidNumber, 10 | validateIsInteger, 11 | validateIsNonNegative, 12 | ]) 13 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/configuration.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`configure() config object baseFontSize adjusts values based on 'basefontSize' for height 1`] = `(max-height: 39.999em)`; 4 | 5 | exports[`configure() config object baseFontSize adjusts values based on 'basefontSize' for width 1`] = `(max-width: 39.999em)`; 6 | -------------------------------------------------------------------------------- /src/__tests__/rangedQueries.js: -------------------------------------------------------------------------------- 1 | import { mqWithValidBreakpointsForRange } from './testHelpers/data' 2 | 3 | describe(`rangedQueries`, () => { 4 | it(`generates ranged queries`, () => { 5 | const mq = mqWithValidBreakpointsForRange(`width`) 6 | const result = mq.queryAboveWidth(`medium`)`` 7 | expect(result).toMatchSnapshot() 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "es5", 7 | "overrides": [ 8 | { 9 | "files": ".prettierrc", 10 | "options": { "parser": "json" } 11 | }, 12 | { 13 | "files": ".babelrc", 14 | "options": { "parser": "json" } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsValidMediaType.js: -------------------------------------------------------------------------------- 1 | import { values } from 'ramda' 2 | import { 3 | orValidator, 4 | validateIsNull, 5 | validateIsWhitelistedValue, 6 | } from 'folktale-validations' 7 | import { MEDIA_TYPES } from '../../const' 8 | 9 | export default orValidator( 10 | validateIsWhitelistedValue(values(MEDIA_TYPES)), 11 | validateIsNull 12 | ) 13 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/matchers/customMatchers.js: -------------------------------------------------------------------------------- 1 | import { toThrowMultiline, toEqualMultiline } from 'jasmine-multiline-matchers' 2 | import { 3 | toEqualSuccessWithValue, 4 | toEqualFailureWithValue, 5 | } from 'jasmine-folktale' 6 | 7 | expect.extend({ 8 | toEqualMultiline, 9 | toThrowMultiline, 10 | toEqualSuccessWithValue, 11 | toEqualFailureWithValue, 12 | }) 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | node_js: 4 | - 6 5 | - 7 6 | - 8 7 | notifications: 8 | email: 9 | on_failure: change 10 | script: 11 | - node --version 12 | - yarn --version 13 | - yarn run lint 14 | - yarn run test:noWatch 15 | - codecov 16 | - yarn run build 17 | cache: 18 | yarn: true 19 | bundler: true 20 | directories: 21 | - node_modules -------------------------------------------------------------------------------- /src/validations/validators/validateIsNegationObject.js: -------------------------------------------------------------------------------- 1 | import { 2 | validateRequiredKeys, 3 | untilFailureValidator, 4 | validateIsPlainObject, 5 | decorateValidator, 6 | } from 'folktale-validations' 7 | import validatorUIDs from '../validatorUIDs' 8 | 9 | export default decorateValidator( 10 | validatorUIDs.VALIDATE_IS_NEGATION_OBJECT, 11 | untilFailureValidator([validateIsPlainObject, validateRequiredKeys([`not`])]) 12 | ) 13 | -------------------------------------------------------------------------------- /src/__tests__/api/__snapshots__/tweakpoints.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`tweaked() doesn't effect the original mq original breakpoints are available 1`] = `(min-width: 25em) and (max-width: 56.249375em)`; 4 | 5 | exports[`tweaked() includes original breakpoints and added tweakpoints 1`] = `(min-width: 18.75em)`; 6 | 7 | exports[`tweaked() includes original breakpoints and added tweakpoints 2`] = `(min-width: 81.25em) and (max-width: 18.749375em)`; 8 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsDimension.js: -------------------------------------------------------------------------------- 1 | import { anyOfValidator } from 'folktale-validations' 2 | import validateIsRem from './validateIsRem' 3 | import validateIsEm from './validateIsEm' 4 | import validateIsPx from './validateIsPx' 5 | import validateIsNonNegativeValidNumber from './validateIsNonNegativeValidNumber' 6 | 7 | export default anyOfValidator([ 8 | validateIsNonNegativeValidNumber, 9 | validateIsRem, 10 | validateIsEm, 11 | validateIsPx, 12 | ]) 13 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsNegationElement.js: -------------------------------------------------------------------------------- 1 | import { isArray } from 'ramda-adjunct' 2 | import { 3 | validateIsArray, 4 | validateArrayElements, 5 | anyOfValidator, 6 | validateIsString, 7 | untilFailureValidator, 8 | whenValidator, 9 | } from 'folktale-validations' 10 | 11 | export default untilFailureValidator([ 12 | anyOfValidator([validateIsString, validateIsArray]), 13 | whenValidator(isArray, validateArrayElements(validateIsString)), 14 | ]) 15 | -------------------------------------------------------------------------------- /src/features/buildLinearFeatures.js: -------------------------------------------------------------------------------- 1 | import { reduce, assoc } from 'ramda' 2 | import camelcase from 'camelcase' 3 | import { LINEAR_FEATURES } from '../features' 4 | 5 | import buildLinearFeature from '../features/buildLinearFeature' 6 | 7 | const reducer = (acc, { name, validValues, allowNoArgument }) => 8 | assoc( 9 | camelcase(name), 10 | buildLinearFeature(name, validValues, allowNoArgument), 11 | acc 12 | ) 13 | 14 | export default () => reduce(reducer, {}, LINEAR_FEATURES) 15 | -------------------------------------------------------------------------------- /src/features/buildRangedQueries.js: -------------------------------------------------------------------------------- 1 | import { assoc, compose } from 'ramda' 2 | 3 | import query from '../api/query' 4 | 5 | import { reduceObjIndexed } from '../utils/object' 6 | import { titleize } from '../utils/string' 7 | import { wrapWithErrorHandler } from '../errors' 8 | 9 | const toQueryName = name => `query${titleize(name)}` 10 | 11 | export default reduceObjIndexed((acc, [name, f]) => { 12 | const fName = toQueryName(name) 13 | return assoc(fName, wrapWithErrorHandler(fName, compose(query, f)), acc) 14 | }, {}) 15 | -------------------------------------------------------------------------------- /src/validations/validatorMessages.js: -------------------------------------------------------------------------------- 1 | import { always } from 'ramda' 2 | import VALIDATOR_UIDS from './validatorUIDs' 3 | 4 | const { 5 | VALIDATE_IS_ASPECT_RATIO_STRING, 6 | VALIDATE_IS_NEGATION_OBJECT, 7 | } = VALIDATOR_UIDS 8 | 9 | const messages = { 10 | [VALIDATE_IS_ASPECT_RATIO_STRING]: always( 11 | `must be an aspect ratio string, in the form of two positive integers separated by a forward slash, for example '16/9'` 12 | ), 13 | [VALIDATE_IS_NEGATION_OBJECT]: always(`Wasn't negation (not) object`), 14 | } 15 | 16 | export default messages 17 | -------------------------------------------------------------------------------- /src/validations/validators/validateIsQueryElement.js: -------------------------------------------------------------------------------- 1 | import { isArray } from 'ramda-adjunct' 2 | import { 3 | validateIsArray, 4 | validateArrayElements, 5 | anyOfValidator, 6 | validateIsString, 7 | untilFailureValidator, 8 | whenValidator, 9 | } from 'folktale-validations' 10 | import validateIsNegationObject from './validateIsNegationObject' 11 | 12 | export default untilFailureValidator([ 13 | anyOfValidator([validateIsString, validateIsArray, validateIsNegationObject]), 14 | whenValidator(isArray, validateArrayElements(validateIsString)), 15 | ]) 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | modulePaths: [`/src/__tests__`], 3 | bail: true, 4 | verbose: false, 5 | collectCoverage: false, 6 | collectCoverageFrom: [`src/**/*.js`], 7 | coveragePathIgnorePatterns: [`src/index.js`], 8 | coverageReporters: [`lcov`, `html`], 9 | setupFiles: [], 10 | modulePathIgnorePatterns: [`testHelpers/`, `sharedTests/`], 11 | setupTestFrameworkScriptFile: `/src/__tests__/testHelpers/matchers/customMatchers.js`, 12 | unmockedModulePathPatterns: [`jasmine-expect`], 13 | // reporters: [ 14 | // [ 15 | // `jest-slow-test-reporter`, 16 | // { numTests: 8, warnOnSlowerThan: 300, color: true }, 17 | // ], 18 | // ], 19 | } 20 | -------------------------------------------------------------------------------- /src/__tests__/api/__snapshots__/mediaType.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`mediaTypes returns default media type if called with an empty array 1`] = `screen`; 4 | 5 | exports[`mediaTypes returns default media type if called with no arguments 1`] = `screen`; 6 | 7 | exports[`mediaTypes returns the supplied mediaTypes for 'all' 1`] = `all`; 8 | 9 | exports[`mediaTypes returns the supplied mediaTypes for 'print' 1`] = `print`; 10 | 11 | exports[`mediaTypes returns the supplied mediaTypes for 'screen' 1`] = `screen`; 12 | 13 | exports[`mediaTypes returns the supplied mediaTypes for 'speech' 1`] = `speech`; 14 | 15 | exports[`mediaTypes supports multiple values 1`] = `screen, speech`; 16 | -------------------------------------------------------------------------------- /src/features/buildLinearFeature.js: -------------------------------------------------------------------------------- 1 | import { compose, prop } from 'ramda' 2 | import { matchWithSuccessOrFailure, toArgsObj } from 'folktale-validations' 3 | import { renderFeature } from '../renderers/cssRenderers/queryRenderer' 4 | import { throwAPILinearFeatureError } from '../errors2' 5 | import validatorForLinearFeature from '../validations/validatorForLinearFeature' 6 | 7 | export default (name, possibleValues, allowNoArgument = false) => value => 8 | compose( 9 | matchWithSuccessOrFailure( 10 | compose(renderFeature(name), prop(`value`), prop(`value`)), 11 | compose(throwAPILinearFeatureError(name), prop(`value`)) 12 | ), 13 | validatorForLinearFeature(possibleValues, !allowNoArgument) 14 | )(toArgsObj({ value })) 15 | -------------------------------------------------------------------------------- /src/api/not.js: -------------------------------------------------------------------------------- 1 | import { matchWithSuccessOrFailure, toArgsObj } from 'folktale-validations' 2 | import { compose, prop } from 'ramda' 3 | import validateAPINot from '../validations/validators/args/validateAPINot' 4 | import { throwAPINotError } from '../errors2' 5 | import { renderNotQueryDefinition } from '../renderers/cssRenderers/queryRenderer' 6 | 7 | export default defaultMediaType => (...elements) => 8 | compose( 9 | matchWithSuccessOrFailure( 10 | compose( 11 | renderNotQueryDefinition(defaultMediaType), 12 | prop(`elements`), 13 | prop(`value`) 14 | ), 15 | compose(throwAPINotError, prop(`value`)) 16 | ), 17 | validateAPINot 18 | )( 19 | toArgsObj({ 20 | elements, 21 | }) 22 | ) 23 | -------------------------------------------------------------------------------- /src/api/tweak.js: -------------------------------------------------------------------------------- 1 | import { compose, prop, mergeDeepLeft } from 'ramda' 2 | import { matchWithSuccessOrFailure, toArgsObj } from 'folktale-validations' 3 | import configure from '../mq' 4 | import { throwAPITweakError } from '../errors2' 5 | import validateAPITweak from '../validations/validators/args/validateAPITweak' 6 | 7 | export default (mq, breakpoints, config) => tweakpoints => 8 | compose( 9 | matchWithSuccessOrFailure( 10 | compose( 11 | v => configure(v, config, mq), 12 | mergeDeepLeft(breakpoints), 13 | prop(`tweakpoints`), 14 | prop(`value`) 15 | ), 16 | compose(throwAPITweakError, prop(`value`)) 17 | ), 18 | validateAPITweak 19 | )( 20 | toArgsObj({ 21 | tweakpoints, 22 | }) 23 | ) 24 | -------------------------------------------------------------------------------- /src/validations/transformers/resolutionTransformer.js: -------------------------------------------------------------------------------- 1 | import { isNumberWithDpi } from 'cssapi-units' 2 | import { always, compose, when, flip, subtract } from 'ramda' 3 | import { numericPartOfUnitedNumber, appendUnit } from 'cssjs-units' 4 | import { separatorValueForUnit } from '../../utils/units' 5 | import { UNITS } from '../../const' 6 | 7 | const toUnit = flip(appendUnit)(UNITS.RESOLUTION.DPI) 8 | 9 | export default ({ 10 | shouldSeparateQueries = true, 11 | canSeparateQueries = false, 12 | } = {}) => value => 13 | compose( 14 | toUnit, 15 | when( 16 | always(canSeparateQueries && shouldSeparateQueries), 17 | flip(subtract)(separatorValueForUnit(UNITS.RESOLUTION.DPI)) 18 | ), 19 | when(isNumberWithDpi, numericPartOfUnitedNumber) 20 | )(value) 21 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/toCSS.js: -------------------------------------------------------------------------------- 1 | import { compose, trim, replace, ifElse } from 'ramda' 2 | import { isArray } from 'ramda-adjunct' 3 | import cssbeautify from 'cssbeautify' 4 | import { joinWithNoSpace } from '../../utils/string' 5 | 6 | const R_GLOBAL_WHITESPACE = /\s\s+/g 7 | const R_GLOBAL_BLANK_LINES = /^\s*\n/gm 8 | const R_GLOBAL_COMMENTS = /^\s*\/\/.*$/gm 9 | 10 | const collapseSpaces = replace(R_GLOBAL_WHITESPACE, ` `) 11 | const removeBlankLines = replace(R_GLOBAL_BLANK_LINES, ``) 12 | const removeComments = replace(R_GLOBAL_COMMENTS, ``) 13 | 14 | const prepString = compose(cssbeautify, removeBlankLines, collapseSpaces, trim) 15 | const stringifyRules = compose(prepString, removeComments, joinWithNoSpace) 16 | 17 | export default ifElse(isArray, stringifyRules, prepString) 18 | -------------------------------------------------------------------------------- /src/mq.js: -------------------------------------------------------------------------------- 1 | import { 2 | matchWithSuccessOrFailure, 3 | toArgsObj, 4 | fromArgsObj, 5 | } from 'folktale-validations' 6 | import { compose, prop, apply } from 'ramda' 7 | import api from './api' 8 | import validateMQArgs from './validations/validators/args/validateMQArgs' 9 | import { throwConfigureError } from './errors2' 10 | 11 | export default (breakpoints, config, originalMQ) => 12 | compose( 13 | matchWithSuccessOrFailure( 14 | compose( 15 | apply(api), 16 | fromArgsObj([`breakpoints`, `config`, `originalMQ`]), 17 | prop(`value`) 18 | ), 19 | compose(throwConfigureError, prop(`value`)) 20 | ), 21 | validateMQArgs 22 | )( 23 | toArgsObj({ 24 | breakpoints, 25 | config, 26 | originalMQ, 27 | }) 28 | ) 29 | -------------------------------------------------------------------------------- /src/features/buildRangedFeatures.js: -------------------------------------------------------------------------------- 1 | import { reduce, merge, prop } from 'ramda' 2 | import camelcase from 'camelcase' 3 | import { RANGED_FEATURES } from '../features' 4 | import buildRangedFeature from './buildRangedFeature' 5 | 6 | const breakpointMapNamed = (name, breakpoints) => 7 | prop(camelcase(name), breakpoints) 8 | 9 | const reducer = (globalConfig, breakpoints = {}) => ( 10 | acc, 11 | { name, featureConfig, validator, transformer } 12 | ) => { 13 | const feat = buildRangedFeature( 14 | name, 15 | validator, 16 | transformer, 17 | breakpointMapNamed(name, breakpoints), 18 | merge(globalConfig, featureConfig) 19 | ) 20 | return merge(acc, feat) 21 | } 22 | 23 | export default (breakpoints, globalConfig) => 24 | reduce(reducer(globalConfig, breakpoints), {}, RANGED_FEATURES) 25 | -------------------------------------------------------------------------------- /src/__tests__/utils/value.js: -------------------------------------------------------------------------------- 1 | import { map } from 'ramda' 2 | import { 3 | isPositiveNumberWithDimensionsUnit, 4 | isPositiveIntegerOrZero, 5 | } from '../../utils/predicates' 6 | 7 | describe(`utils`, () => { 8 | describe(`isPositiveNumberWithDimensionsUnit`, () => { 9 | const validValues = [`14em`, `10px`, `16rem`] 10 | map(value => { 11 | it(`returns true for valid value '${value}'`, () => { 12 | expect(isPositiveNumberWithDimensionsUnit(value)).toBeTruthy() 13 | }) 14 | })(validValues) 15 | }) 16 | 17 | describe(`isPositiveIntegerOrZero`, () => { 18 | const validValues = [0, 44, 66, 77] 19 | map(value => { 20 | it(`returns true for valid value '${value}'`, () => { 21 | expect(isPositiveIntegerOrZero(value)).toBeTruthy() 22 | }) 23 | })(validValues) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/api/mediaType.js: -------------------------------------------------------------------------------- 1 | import { when, append, prop, objOf, compose } from 'ramda' 2 | import { ensureArray, isEmptyArray } from 'ramda-adjunct' 3 | import { matchWithSuccessOrFailure } from 'folktale-validations' 4 | import { throwAPIMediaTypeError } from '../errors2' 5 | import validateAPIMediaTypeArgs from '../validations/validators/args/validateAPIMediaTypeArgs' 6 | import { toCommaSeparatedList } from '../utils/string' 7 | 8 | export default defaultMediaType => (mediaTypes = [defaultMediaType]) => 9 | compose( 10 | matchWithSuccessOrFailure( 11 | compose(toCommaSeparatedList, prop(`mediaTypes`), prop(`value`)), 12 | compose(throwAPIMediaTypeError, prop(`value`)) 13 | ), 14 | validateAPIMediaTypeArgs, 15 | objOf(`mediaTypes`), 16 | when(isEmptyArray, append(defaultMediaType)), 17 | ensureArray 18 | )(mediaTypes) 19 | -------------------------------------------------------------------------------- /src/__tests__/sharedTests/config.js: -------------------------------------------------------------------------------- 1 | import { mqWithValidBreakpointsForRange } from '../testHelpers/data' 2 | 3 | export const configOutputsConfiguredDimensionUnits = (name, method) => { 4 | it(`renders configured dimensionsUnits`, () => { 5 | expect( 6 | mqWithValidBreakpointsForRange(name, { dimensionsUnit: `rem` })[method]( 7 | `small` 8 | ) 9 | ).toMatchSnapshot() 10 | 11 | expect( 12 | mqWithValidBreakpointsForRange(name, { dimensionsUnit: `px` })[method]( 13 | `small` 14 | ) 15 | ).toMatchSnapshot() 16 | }) 17 | } 18 | 19 | export const configSeparatesValuesWhenSet = (name, method) => { 20 | it(`doesn't separate values if not configured to do so`, () => { 21 | expect( 22 | mqWithValidBreakpointsForRange(name, { shouldSeparateQueries: false })[ 23 | method 24 | ](`small`) 25 | ).toMatchSnapshot() 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/api/query.js: -------------------------------------------------------------------------------- 1 | import { matchWithSuccessOrFailure, toArgsObj } from 'folktale-validations' 2 | import { css } from 'styled-components' 3 | import { compose, prop } from 'ramda' 4 | import validateAPIQuery from '../validations/validators/args/validateAPIQuery' 5 | import { throwAPIQueryError } from '../errors2' 6 | import { renderQueryDefinition } from '../renderers/cssRenderers/queryRenderer' 7 | import renderQuery from '../renderers/cssRenderers/styledComponentsRenderer' 8 | 9 | const templateFunction = elements => (stringParts, ...interpolationValues) => 10 | renderQuery( 11 | renderQueryDefinition(...elements), 12 | css(stringParts, ...interpolationValues) 13 | ) 14 | 15 | export default (...elements) => 16 | compose( 17 | matchWithSuccessOrFailure( 18 | compose(templateFunction, prop(`elements`), prop(`value`)), 19 | compose(throwAPIQueryError, prop(`value`)) 20 | ), 21 | validateAPIQuery 22 | )( 23 | toArgsObj({ 24 | elements, 25 | }) 26 | ) 27 | -------------------------------------------------------------------------------- /src/utils/features.js: -------------------------------------------------------------------------------- 1 | import { map, compose } from 'ramda' 2 | import camelcase from 'camelcase' 3 | import { matchWithSuccessOrFailure } from 'folktale-validations' 4 | import { renderFeature } from '../renderers/cssRenderers/queryRenderer' 5 | import { propValue } from './validations' 6 | import { throwAPILinearFeatureInvalidValueError } from '../errors2' 7 | import { RANGED_FEATURES } from '../features' 8 | import { propName } from './breakpoints' 9 | 10 | export const buildFeatureItem = (featureName, valueRenderer, config) => value => 11 | renderFeature(featureName, valueRenderer(featureName, value, config)) 12 | 13 | export const validateAndTransform = (validator, transformer, config) => value => 14 | compose( 15 | transformer(config), 16 | matchWithSuccessOrFailure( 17 | propValue, 18 | compose(throwAPILinearFeatureInvalidValueError, propValue) 19 | ), 20 | validator 21 | )(value) 22 | 23 | export const rangedFeatureNames = map(compose(camelcase, propName))( 24 | RANGED_FEATURES 25 | ) 26 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable ramda/no-redundant-not */ 2 | 3 | import mediaType from './api/mediaType' 4 | import buildLinearFeatures from './features/buildLinearFeatures' 5 | import buildRangeFeatures from './features/buildRangedFeatures' 6 | 7 | import tweak from './api/tweak' 8 | import query from './api/query' 9 | import not from './api/not' 10 | import untweaked from './api/untweaked' 11 | import buildRangedQueries from './features/buildRangedQueries' 12 | 13 | export default (breakpoints, config, originalMQ) => { 14 | const { defaultMediaType } = config 15 | const linearFeatures = buildLinearFeatures() 16 | const rangedFeatures = buildRangeFeatures(breakpoints, config) 17 | const rangedQueries = buildRangedQueries(rangedFeatures) 18 | 19 | const mq = { 20 | mediaType: mediaType(defaultMediaType), 21 | ...linearFeatures, 22 | ...rangedFeatures, 23 | ...rangedQueries, 24 | query, 25 | not: not(defaultMediaType), 26 | untweaked: untweaked(originalMQ), 27 | } 28 | mq.tweak = tweak(mq, breakpoints, config) 29 | return mq 30 | } 31 | -------------------------------------------------------------------------------- /src/validations/transformers/dimensionTransformer.js: -------------------------------------------------------------------------------- 1 | import { always, compose, when, curry, flip, subtract, partial } from 'ramda' 2 | import { outputWithUnit } from 'cssjs-units' 3 | import { unitedDimensionToUnitlessPixelValue } from 'cssapi-units' 4 | import { separatorValueForUnit } from '../../utils/units' 5 | import { isNumberWithDimensionsUnit } from '../../utils/predicates' 6 | import { UNITS } from '../../const' 7 | 8 | const toUnit = (dimensionsUnit, baseFontSize) => 9 | partial(outputWithUnit, [dimensionsUnit, baseFontSize]) 10 | 11 | export default ({ 12 | baseFontSize = 16, 13 | dimensionsUnit = UNITS.DIMENSIONS.EM, 14 | shouldSeparateQueries = true, 15 | canSeparateQueries = false, 16 | } = {}) => value => 17 | compose( 18 | toUnit(dimensionsUnit, baseFontSize), 19 | when( 20 | always(canSeparateQueries && shouldSeparateQueries), 21 | flip(subtract)(separatorValueForUnit(dimensionsUnit)) 22 | ), 23 | when( 24 | isNumberWithDimensionsUnit, 25 | curry(flip(unitedDimensionToUnitlessPixelValue))(baseFontSize) 26 | ) 27 | )(value) 28 | -------------------------------------------------------------------------------- /src/utils/string.js: -------------------------------------------------------------------------------- 1 | import { 2 | map, 3 | append, 4 | prepend, 5 | compose, 6 | join, 7 | of, 8 | concat, 9 | over, 10 | lensIndex, 11 | toUpper, 12 | } from 'ramda' 13 | 14 | export const joinWithNoSpace = join(``) 15 | export const joinWithCommaSpace = join(`, `) 16 | export const joinWithComma = join(`,`) 17 | export const joinWithSpace = join(` `) 18 | export const joinWithColon = join(`: `) 19 | 20 | export const wrapWith = (a, b = a) => 21 | compose(joinWithNoSpace, prepend(a), append(b), of) 22 | 23 | export const wrapWithSquareBrackets = wrapWith(`[`, `]`) 24 | export const wrapWithSoftBrackets = wrapWith(`(`, `)`) 25 | export const wrapWithSingleQuotes = wrapWith(`'`) 26 | 27 | export const toCommaSeparatedList = values => values.join(`, `) 28 | 29 | export const quoteAndJoinWithComma = compose( 30 | joinWithCommaSpace, 31 | map(wrapWithSingleQuotes) 32 | ) 33 | 34 | export const toList = compose(wrapWithSquareBrackets, quoteAndJoinWithComma) 35 | 36 | export const prefixWithNot = concat(`not `) 37 | 38 | export const titleize = compose(joinWithNoSpace, over(lensIndex(0), toUpper)) 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pedr Browne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/__tests__/validations/transformers/resolutionTransformer.js: -------------------------------------------------------------------------------- 1 | import resolutionTransformer from '../../../validations/transformers/resolutionTransformer' 2 | 3 | describe(`resolutionTransformer()`, () => { 4 | describe(`with default config`, () => { 5 | describe(`with unitless value`, () => { 6 | it(`returns the correct value`, () => { 7 | const value = 10 8 | const result = resolutionTransformer()(value) 9 | expect(result).toEqual(`10dpi`) 10 | }) 11 | }) 12 | 13 | describe(`with united em value`, () => { 14 | it(`returns the correct value`, () => { 15 | const value = `10dpi` 16 | const result = resolutionTransformer()(value) 17 | expect(result).toEqual(`10dpi`) 18 | }) 19 | }) 20 | 21 | describe(`with canSeparateQueries set to true`, () => { 22 | it(`returns the correct value`, () => { 23 | const value = `10dpi` 24 | const result = resolutionTransformer({ 25 | shouldSeparateQueries: true, 26 | canSeparateQueries: true, 27 | })(value) 28 | expect(result).toEqual(`9dpi`) 29 | }) 30 | }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/__tests__/validations/transformers/dimensionTransformer.js: -------------------------------------------------------------------------------- 1 | import dimensionTransformer from '../../../validations/transformers/dimensionTransformer' 2 | 3 | describe(`dimensionTransformer()`, () => { 4 | describe(`with default config`, () => { 5 | describe(`with unitless value`, () => { 6 | it(`returns the correct value`, () => { 7 | const value = 10 8 | const result = dimensionTransformer()(value) 9 | expect(result).toEqual(`0.625em`) 10 | }) 11 | }) 12 | 13 | describe(`with united em value`, () => { 14 | it(`returns the correct value`, () => { 15 | const value = `0.625em` 16 | const result = dimensionTransformer()(value) 17 | expect(result).toEqual(`0.625em`) 18 | }) 19 | }) 20 | 21 | describe(`with canSeparateQueries set to true`, () => { 22 | it(`returns the correct value`, () => { 23 | const value = `0.625em` 24 | const result = dimensionTransformer({ 25 | shouldSeparateQueries: true, 26 | canSeparateQueries: true, 27 | })(value) 28 | expect(result).toEqual(`0.624375em`) 29 | }) 30 | }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | import { keys, compose, curry, map, tryCatch } from 'ramda' 2 | import { toCommaSeparatedList } from './utils/string' 3 | import { throwScopedError } from './errors2' 4 | 5 | const wrapWithQuotes = map(v => `'${v}'`) 6 | const keysToCommaSeparatedList = compose( 7 | toCommaSeparatedList, 8 | wrapWithQuotes, 9 | keys 10 | ) 11 | 12 | // ----------------------------------------------------------------------------- 13 | // Exports 14 | // ----------------------------------------------------------------------------- 15 | 16 | export const throwError = message => { 17 | throw new Error(message) 18 | } 19 | 20 | export const missingBreakpointErrorMessage = curry( 21 | (name, breakpointMapName, breakpoints) => 22 | `There is no '${breakpointMapName}' breakpoint defined called '${name}', only: ${keysToCommaSeparatedList( 23 | breakpoints 24 | )} are defined` 25 | ) 26 | 27 | export const sameBreakpointsForBetweenErrorMessage = name => 28 | `You must supply two different breakpoints but both were: '${name}'.` 29 | 30 | export const wrapWithErrorHandler = (fName, f) => 31 | tryCatch(f, ({ message }) => throwScopedError(fName, message)) 32 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/testRangedFeatureHelpers.js: -------------------------------------------------------------------------------- 1 | import camelcase from 'camelcase' 2 | import runTests from './runTests' 3 | 4 | const pluralise = value => `${value}s` 5 | 6 | export default (name, { tests = [] } = {}) => { 7 | describe(`${name}`, () => { 8 | describe(`range queries`, () => { 9 | const aboveMethod = camelcase(`above`, name) 10 | const belowMethod = camelcase(`below`, name) 11 | const betweenMethod = camelcase(`between`, pluralise(name)) 12 | const atMethod = camelcase(`at`, name) 13 | const atBreakpointMethod = camelcase(`at`, name, `Breakpoint`) 14 | 15 | describe(`${aboveMethod}()`, () => { 16 | runTests(tests.above, name, aboveMethod) 17 | }) 18 | 19 | describe(`${belowMethod}()`, () => { 20 | runTests(tests.below, name, belowMethod) 21 | }) 22 | 23 | describe(`${betweenMethod}()`, () => { 24 | runTests(tests.between, name, betweenMethod) 25 | }) 26 | 27 | describe(`${atMethod}()`, () => { 28 | runTests(tests.at, name, atMethod) 29 | }) 30 | 31 | describe(`${atBreakpointMethod}()`, () => { 32 | runTests(tests.atBreakpoint, name, atBreakpointMethod) 33 | }) 34 | }) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/breakpoints.js: -------------------------------------------------------------------------------- 1 | import { 2 | inc, 3 | compose, 4 | map, 5 | zipObj, 6 | prop, 7 | toPairs, 8 | findIndex, 9 | propEq, 10 | nth, 11 | isNil, 12 | isEmpty, 13 | } from 'ramda' 14 | 15 | import { throwError, missingBreakpointErrorMessage } from '../errors' 16 | 17 | import { throwMissingBreakpointSetErrorMessage } from '../errors2' 18 | 19 | const NAME = `name` 20 | const VALUE = `value` 21 | 22 | export const propEqName = propEq(NAME) 23 | export const propName = prop(NAME) 24 | 25 | const zipToNameAndValue = zipObj([NAME, VALUE]) 26 | 27 | const findBreakpointIndex = (breakpoint, breakpointsArray) => 28 | findIndex(propEqName(breakpoint))(breakpointsArray) 29 | 30 | export const toBreakpointArray = compose(map(zipToNameAndValue), toPairs) 31 | 32 | export const getUpperLimit = breakpointsArray => breakpoint => { 33 | const index = findBreakpointIndex(breakpoint, breakpointsArray) 34 | return compose(propName, nth(inc(index)))(breakpointsArray) 35 | } 36 | 37 | export const getBreakpointNamed = ( 38 | featureName, 39 | methodName, 40 | breakpointSet 41 | ) => breakpointName => { 42 | if (isEmpty(breakpointSet)) throwMissingBreakpointSetErrorMessage(featureName) 43 | const value = breakpointSet[breakpointName] 44 | if (isNil(value)) 45 | throwError( 46 | missingBreakpointErrorMessage(breakpointName, featureName, breakpointSet) 47 | ) 48 | return value 49 | } 50 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint-config-airbnb-base', 4 | 'prettier', 5 | 'plugin:ramda/recommended', 6 | ], 7 | plugins: ['prettier', 'eslint-plugin-ramda'], 8 | env: { 9 | browser: true, 10 | jest: true, 11 | }, 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | rules: { 19 | 'func-names': ['error', 'never'], 20 | 'no-param-reassign': 'off', 21 | 'import/no-extraneous-dependencies': 'off', 22 | 'no-confusing-arrow': 'off', 23 | 'no-unused-expressions': [ 24 | 'error', 25 | { 26 | allowTaggedTemplates: true, 27 | }, 28 | ], 29 | quotes: [ 30 | 'error', 31 | 'backtick', 32 | { avoidEscape: true, allowTemplateLiterals: true }, 33 | ], 34 | 'jsx-quotes': ['error', 'prefer-double'], 35 | 'comma-dangle': ['error', 'always-multiline'], 36 | 'valid-jsdoc': ['error'], 37 | 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], 38 | 'import/extensions': ['off', 'never'], 39 | 'no-unused-vars': [ 40 | 'error', 41 | { 42 | argsIgnorePattern: '^_$', 43 | }, 44 | ], 45 | 'no-console': ['error'], 46 | 'ramda/always-simplification': ['error'], 47 | 'ramda/compose-simplification': ['error'], 48 | 'ramda/eq-by-simplification': ['error'], 49 | 'ramda/prefer-complement': ['error'], 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /src/__tests__/rangedFeatureHelpers.js: -------------------------------------------------------------------------------- 1 | import { map } from 'ramda' 2 | import testRangedFeatureHelpers from './testHelpers/testRangedFeatureHelpers' 3 | import cssSerialiser from './testHelpers/cssSerialiser' 4 | import { RANGED_FEATURES } from '../features' 5 | 6 | import { 7 | queryThrowsIfMissingBreakpoint, 8 | queryThrowsIfMissingBreakpointSet, 9 | queryReturnsCorrectValueSingleBreakpoint, 10 | queryReturnsCorrectValueWithTwoBreakpoints, 11 | queryThrowsIfMissingEitherBreakpoint, 12 | queryThrowsWithBothBreakpointsTheSame, 13 | } from './sharedTests/rangedFeatureHelpers' 14 | 15 | // Add serialiser for generating readable snapshots from CSS 16 | expect.addSnapshotSerializer(cssSerialiser) 17 | 18 | const singleArgumentSharedTest = [ 19 | queryThrowsIfMissingBreakpoint, 20 | queryThrowsIfMissingBreakpointSet, 21 | queryReturnsCorrectValueSingleBreakpoint, 22 | ] 23 | 24 | describe(`ranged feature helpers`, () => { 25 | map(feature => { 26 | testRangedFeatureHelpers(feature.name, { 27 | tests: { 28 | above: [...singleArgumentSharedTest], 29 | below: [...singleArgumentSharedTest], 30 | between: [ 31 | queryReturnsCorrectValueWithTwoBreakpoints, 32 | queryThrowsIfMissingEitherBreakpoint, 33 | queryThrowsWithBothBreakpointsTheSame, 34 | ], 35 | at: [...singleArgumentSharedTest], 36 | atBreakpoint: [...singleArgumentSharedTest], 37 | }, 38 | }) 39 | })(RANGED_FEATURES) 40 | }) 41 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/testRangedFeature.js: -------------------------------------------------------------------------------- 1 | import camelcase from 'camelcase' 2 | import runTests from './runTests' 3 | 4 | export default ( 5 | name, 6 | { 7 | tests = [], 8 | validExplicitValues = [], 9 | invalidNonExplicitValues = [], 10 | invalidExplicitValues = [], 11 | allowNoArgument = false, 12 | } = {} 13 | ) => { 14 | const camelisedName = camelcase(name) 15 | describe(`${name}`, () => { 16 | describe(`range features`, () => { 17 | // Define accessor names 18 | const valueMethod = camelisedName 19 | const minValueMethod = camelcase(`min`, name) 20 | const maxValueMethod = camelcase(`max`, name) 21 | 22 | describe(`${valueMethod}()`, () => { 23 | runTests(tests.value, name, camelisedName, valueMethod, { 24 | invalidNonExplicitValues, 25 | invalidExplicitValues, 26 | validExplicitValues, 27 | allowNoArgument, 28 | }) 29 | }) 30 | 31 | describe(`${minValueMethod}()`, () => { 32 | runTests(tests.minValue, name, camelisedName, minValueMethod, { 33 | invalidNonExplicitValues, 34 | invalidExplicitValues, 35 | validExplicitValues, 36 | }) 37 | }) 38 | 39 | describe(`${maxValueMethod}()`, () => { 40 | runTests(tests.maxValue, name, camelisedName, maxValueMethod, { 41 | invalidNonExplicitValues, 42 | invalidExplicitValues, 43 | validExplicitValues, 44 | }) 45 | }) 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/__tests__/validations/validators/validateIsNegationObject.js: -------------------------------------------------------------------------------- 1 | import validateIsNegationObject from '../../../validations/validators/validateIsNegationObject' 2 | 3 | describe(`validateIsNegationObject()`, () => { 4 | it(`returns a Validation.Success if the value is a negation object`, () => { 5 | const value = { 6 | not: `not screen and (display-mode: fullscreen) and (color-gamut: p3) and (orientation: landscape)`, 7 | } 8 | const result = validateIsNegationObject(value) 9 | expect(result).toEqualSuccessWithValue(value) 10 | }) 11 | 12 | describe(`failures`, () => { 13 | it(`returns a Validation.Failure if the value not a negation object`, () => { 14 | const value = {} 15 | const result = validateIsNegationObject(value) 16 | expect(result).toEqualFailureWithValue({ 17 | args: [[`not`], [`not`]], 18 | uid: `cssapi-mq.validateIsNegationObject`, 19 | value: {}, 20 | }) 21 | }) 22 | 23 | it(`returns a Validation.Failure for 'null'`, () => { 24 | const value = null 25 | const result = validateIsNegationObject(value) 26 | expect(result).toEqualFailureWithValue({ 27 | args: [], 28 | uid: `cssapi-mq.validateIsNegationObject`, 29 | value: null, 30 | }) 31 | }) 32 | 33 | it(`returns a Validation.Failure for 'undefined'`, () => { 34 | const value = undefined 35 | const result = validateIsNegationObject(value) 36 | expect(result).toEqualFailureWithValue({ 37 | args: [], 38 | uid: `cssapi-mq.validateIsNegationObject`, 39 | value: undefined, 40 | }) 41 | }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/__tests__/api/mediaType.js: -------------------------------------------------------------------------------- 1 | import { values, drop, map } from 'ramda' 2 | import { MEDIA_TYPES } from '../../const' 3 | import { mqWithValidBreakpointsForRange } from '../testHelpers/data' 4 | import cssSerialiser from '../testHelpers/cssSerialiser' 5 | 6 | expect.addSnapshotSerializer(cssSerialiser) 7 | const validValues = values(MEDIA_TYPES) 8 | 9 | describe(`mediaTypes`, () => { 10 | it(`returns default media type if called with no arguments`, () => { 11 | expect( 12 | mqWithValidBreakpointsForRange(`width`).mediaType() 13 | ).toMatchSnapshot() 14 | }) 15 | 16 | it(`returns default media type if called with an empty array`, () => { 17 | expect( 18 | mqWithValidBreakpointsForRange(`width`).mediaType([]) 19 | ).toMatchSnapshot() 20 | }) 21 | 22 | map(value => { 23 | it(`returns the supplied mediaTypes for '${value}'`, () => { 24 | expect( 25 | mqWithValidBreakpointsForRange(`width`).mediaType(value) 26 | ).toMatchSnapshot() 27 | }) 28 | })(validValues) 29 | 30 | it(`supports multiple values`, () => { 31 | expect( 32 | mqWithValidBreakpointsForRange(`width`).mediaType(drop(2, validValues)) 33 | ).toMatchSnapshot() 34 | }) 35 | const invalidMediaTypes = [``, true, false, `xxxx`, 444, {}] 36 | 37 | it(`throws if argument is invalid`, () => { 38 | map(value => { 39 | expect(() => mqWithValidBreakpointsForRange(`width`).mediaType(value)) 40 | .toThrowMultiline(` 41 | [cssapi-mq] mediaType() Arguments included invalid value(s) 42 | – mediaTypes: Array included invalid value(s) 43 | – [0] Value wasn't on the whitelist: ['all', 'print', 'screen', 'speech'] or Wasn't null`) 44 | })(invalidMediaTypes) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /src/utils/predicates.js: -------------------------------------------------------------------------------- 1 | import { 2 | __, 3 | compose, 4 | both, 5 | complement, 6 | isEmpty, 7 | values, 8 | contains, 9 | flip, 10 | all, 11 | has, 12 | } from 'ramda' 13 | import { 14 | isValidPositiveNumber, 15 | isNumberWithUnit, 16 | isNumberWithDpi, 17 | numericPartOfUnitedNumber, 18 | } from 'cssjs-units' 19 | 20 | import { isPlainObj, isNonNegative, isInteger, isPositive } from 'ramda-adjunct' 21 | 22 | import { DIMENSIONS_UNITS, MEDIA_TYPES } from '../const' 23 | 24 | export const isNonEmptyObject = both(complement(isEmpty), isPlainObj) 25 | export const isPositiveInteger = both(isPositive, isInteger) 26 | export const isPositiveIntegerOrZero = both(isNonNegative, isInteger) 27 | 28 | export const isNumericPartOfUnitValuePositive = compose( 29 | isValidPositiveNumber, 30 | numericPartOfUnitedNumber 31 | ) 32 | 33 | export const isPositiveNumberWithPixelUnit = both( 34 | isNumberWithUnit([DIMENSIONS_UNITS.PX]), 35 | isNumericPartOfUnitValuePositive 36 | ) 37 | 38 | export const isNumberWithDimensionsUnit = isNumberWithUnit( 39 | values(DIMENSIONS_UNITS) 40 | ) 41 | 42 | export const isPositiveNumberWithResolutionUnit = both( 43 | isNumberWithDpi, 44 | isNumericPartOfUnitValuePositive 45 | ) 46 | 47 | export const isPositiveNumberWithDimensionsUnit = both( 48 | isNumberWithDimensionsUnit, 49 | isNumericPartOfUnitValuePositive 50 | ) 51 | 52 | export const doesListIncludeValue = list => contains(__, values(list)) 53 | export const isDimensionsUnitValid = contains(__, values(DIMENSIONS_UNITS)) 54 | export const isMediaTypeValid = flip(contains)(values(MEDIA_TYPES)) 55 | export const areMediaTypesValid = both( 56 | all(isMediaTypeValid), 57 | complement(isEmpty) 58 | ) 59 | 60 | export const isNegationObject = both(isPlainObj, has(`not`)) 61 | -------------------------------------------------------------------------------- /src/renderers/cssRenderers/queryRenderer.js: -------------------------------------------------------------------------------- 1 | import { 2 | reject, 3 | compose, 4 | isNil, 5 | when, 6 | map, 7 | any, 8 | contains, 9 | values, 10 | curry, 11 | prop, 12 | flip, 13 | prepend, 14 | cond, 15 | T, 16 | unless, 17 | of, 18 | identity, 19 | } from 'ramda' 20 | 21 | import { isArray } from 'ramda-adjunct' 22 | 23 | import { MEDIA_PREFIX, MEDIA_TYPES } from '../../const' 24 | import { joinWithAnd } from '../../utils/query' 25 | import { 26 | joinWithColon, 27 | joinWithCommaSpace, 28 | prefixWithNot, 29 | joinWithSpace, 30 | wrapWithSoftBrackets, 31 | joinWithComma, 32 | } from '../../utils/string' 33 | import { isNegationObject } from '../../utils/predicates' 34 | 35 | const nameValue = compose(joinWithColon, reject(isNil)) 36 | const expandNegationObject = prop(`not`) 37 | const containsMediaType = flip(contains)(values(MEDIA_TYPES)) 38 | const arrayContainsMediaType = any(containsMediaType) 39 | 40 | const renderElements = map(compose(prefixWithNot, when(isArray, joinWithAnd))) 41 | 42 | const ensureMediaType = (defaultMediaType, ...elements) => 43 | map( 44 | cond([ 45 | [isArray, unless(arrayContainsMediaType, prepend(defaultMediaType))], 46 | [containsMediaType, identity], 47 | [T, compose(joinWithAnd, prepend(defaultMediaType))], 48 | ]) 49 | )(elements) 50 | 51 | export const renderFeature = curry((name, value) => 52 | compose(wrapWithSoftBrackets, nameValue)([name, value]) 53 | ) 54 | 55 | export const renderQueryDefinition = (...elements) => 56 | compose( 57 | joinWithSpace, 58 | prepend(MEDIA_PREFIX), 59 | of, 60 | joinWithComma, 61 | map(when(isArray, joinWithAnd)), 62 | map(when(isNegationObject, expandNegationObject)) 63 | )(elements) 64 | 65 | export const renderNotQueryDefinition = defaultMediaType => (...elements) => 66 | compose(joinWithCommaSpace, renderElements, ensureMediaType)( 67 | defaultMediaType, 68 | ...elements 69 | ) 70 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | import camelcase from 'camelcase' 2 | 3 | export const MEDIA_PREFIX = `@media` 4 | 5 | // ----------------------------------------------------------------------------- 6 | // FEATURE NAMES 7 | // ----------------------------------------------------------------------------- 8 | 9 | export const RANGED_FEATURE_NAMES = Object.freeze({ 10 | WIDTH: `width`, 11 | HEIGHT: `height`, 12 | RESOLUTION: `resolution`, 13 | ASPECT_RATIO: `aspect-ratio`, 14 | COLOR: `color`, 15 | COLOR_INDEX: `color-index`, 16 | MONOCHROME: `monochrome`, 17 | }) 18 | 19 | export const LINEAR_FEATURE_NAMES = Object.freeze({ 20 | ORIENTATION: `orientation`, 21 | SCAN: `scan`, 22 | GRID: `grid`, 23 | UPDATE: `update`, 24 | OVERFLOW_BLOCK: `overflow-block`, 25 | OVERFLOW_INLINE: `overflow-inline`, 26 | COLOR_GAMUT: `color-gamut`, 27 | DISPLAY_MODE: `display-mode`, 28 | }) 29 | 30 | // ----------------------------------------------------------------------------- 31 | // CONFIG 32 | // ----------------------------------------------------------------------------- 33 | 34 | export const SEPARATOR_VALUES = Object.freeze({ 35 | rem: 0.01, 36 | em: 0.01, 37 | px: 1, 38 | dpi: 1, 39 | }) 40 | 41 | export const DIMENSIONS_UNITS = Object.freeze({ 42 | EM: `em`, 43 | REM: `rem`, 44 | PX: `px`, 45 | }) 46 | 47 | export const RESOLUTION_UNIT = `dpi` 48 | 49 | export const UNITS = Object.freeze({ 50 | DIMENSIONS: DIMENSIONS_UNITS, 51 | RESOLUTION: Object.freeze({ DPI: `dpi` }), 52 | }) 53 | 54 | export const BREAKPOINT_MAP_NAMES = Object.freeze([ 55 | `width`, 56 | `height`, 57 | `resolution`, 58 | `aspectRatio`, 59 | `color`, 60 | `colorIndex`, 61 | `monochrome`, 62 | ]) 63 | 64 | // ----------------------------------------------------------------------------- 65 | // MEDIA TYPES 66 | // ----------------------------------------------------------------------------- 67 | 68 | export const MEDIA_TYPES = Object.freeze({ 69 | ALL: `all`, 70 | PRINT: `print`, 71 | SCREEN: `screen`, 72 | SPEECH: `speech`, 73 | }) 74 | 75 | export const ERROR_PREFIX = `[cssapi-mq]` 76 | export const CONFIGURE_PREFIX = `configure()` 77 | export const TWEAK_PREFIX = `tweak()` 78 | export const UNTWEAKED_PREFIX = `untweaked()` 79 | export const API_MEDIA_TYPE_PREFIX = `mediaType()` 80 | export const NOT_PREFIX = `not()` 81 | export const QUERY_PREFIX = `query()` 82 | 83 | export const functionPrefix = name => `${camelcase(name)}()` 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cssapi-mq", 3 | "version": "2.1.0", 4 | "main": "lib/index.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/Undistraction/cssapi-mq.git" 8 | }, 9 | "files": [ 10 | "src", 11 | "lib" 12 | ], 13 | "keywords": [ 14 | "styled-components", 15 | "media-queries", 16 | "css-in-js", 17 | "css", 18 | "breakpoints" 19 | ], 20 | "author": "Pedr Browne", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Undistraction/cssapi-mq/issues" 24 | }, 25 | "homepage": "https://github.com/Undistraction/cssapi-mq", 26 | "scripts": { 27 | "build": "npm run build:lib", 28 | "prebuild:lib": "rimraf lib/*", 29 | "prebuild": "npm run lint", 30 | "build:lib": "babel --out-dir lib --ignore \"__tests__\" src", 31 | "test": "jest --watch", 32 | "test:noWatch": "jest", 33 | "prepublishOnly": "npm run build", 34 | "publish:patch": "npm version patch && sudo npm publish", 35 | "publish:minor": "npm version minor && sudo npm publish", 36 | "publish:major": "npm version major && sudo npm publish", 37 | "lint": "eslint src", 38 | "audit:packages": "yarn outdated || true" 39 | }, 40 | "devDependencies": { 41 | "babel-cli": "^6.26.0", 42 | "babel-core": "6.26.0", 43 | "babel-plugin-external-helpers": "^6.22.0", 44 | "babel-plugin-transform-es2015-destructuring": "^6.23.0", 45 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 46 | "babel-preset-env": "^1.6.1", 47 | "codecov": "^3.0.0", 48 | "cssbeautify": "^0.3.1", 49 | "eslint": "^4.18.2", 50 | "eslint-config-airbnb": "^16.1.0", 51 | "eslint-config-airbnb-base": "^12.1.0", 52 | "eslint-config-prettier": "^2.7.0", 53 | "eslint-plugin-import": "^2.8.0", 54 | "eslint-plugin-jsx-a11y": "^6.0.2", 55 | "eslint-plugin-prettier": "^2.3.1", 56 | "eslint-plugin-ramda": "^2.5.0", 57 | "eslint-plugin-react": "^7.5.1", 58 | "jasmine-folktale": "^0.0.5", 59 | "jasmine-multiline-matchers": "0.2.2", 60 | "jest": "^22.4.2", 61 | "jest-slow-test-reporter": "^1.0.0", 62 | "jest-styled-components": "^4.9.0", 63 | "prettier": "^1.11.1", 64 | "stylelint-config-standard": "^18.2.0", 65 | "stylelint-config-styled-components": "^0.1.1", 66 | "stylelint-processor-styled-components": "^1.3.1", 67 | "stylis": "^3.4.0" 68 | }, 69 | "dependencies": { 70 | "camelcase": "^4.1.0", 71 | "cssapi-units": "^0.3.0", 72 | "cssjs-units": "^0.2.12", 73 | "dasherize": "^2.0.0", 74 | "folktale-validations": "^2.12.1", 75 | "jest-cli": "^22.4.3", 76 | "ramda": "^0.25.0", 77 | "ramda-adjunct": "^2.6.0", 78 | "react": "^16.1.0", 79 | "styled-components": "^2.2.3", 80 | "stylelint": "^9.1.3" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/__tests__/linearFeatures.js: -------------------------------------------------------------------------------- 1 | import { values, map } from 'ramda' 2 | import camelcase from 'camelcase' 3 | import { LINEAR_FEATURES } from '../features' 4 | import featureValues from './testHelpers/featureValues' 5 | import { mqWithValidBreakpointsForRange } from './testHelpers/data' 6 | import cssSerialiser from './testHelpers/cssSerialiser' 7 | import { toList } from '../utils/string' 8 | 9 | expect.addSnapshotSerializer(cssSerialiser) 10 | 11 | describe(`linear features`, () => { 12 | const testLinearFeature = ( 13 | name, 14 | validValuesMap, 15 | invalidValues, 16 | { allowNoArgument = false } = {} 17 | ) => { 18 | const validValues = values(validValuesMap) 19 | const methodName = camelcase(name) 20 | describe(`${methodName}()`, () => { 21 | describe(`linear feature`, () => { 22 | if (allowNoArgument) { 23 | it(`doesn't throw if no argument is supplied`, () => { 24 | expect(() => 25 | mqWithValidBreakpointsForRange(`width`)[methodName]() 26 | ).not.toThrow() 27 | }) 28 | 29 | it(`returns a valueless ${name}`, () => { 30 | expect( 31 | mqWithValidBreakpointsForRange(`width`)[methodName]() 32 | ).toMatchSnapshot() 33 | }) 34 | } else { 35 | it(`throws if no argument is supplied`, () => { 36 | expect(() => mqWithValidBreakpointsForRange(`width`)[methodName]()) 37 | .toThrowMultiline(` 38 | [cssapi-mq] ${methodName}() Arguments missing required key(s): ['value'] 39 | `) 40 | }) 41 | 42 | it(`throws if 'undefined' is supplied`, () => { 43 | expect(() => 44 | mqWithValidBreakpointsForRange(`width`)[methodName](undefined) 45 | ).toThrowMultiline(` 46 | [cssapi-mq] ${methodName}() Arguments missing required key(s): ['value'] 47 | `) 48 | }) 49 | } 50 | 51 | it(`throws if argument is invalid`, () => { 52 | map(value => { 53 | expect(() => 54 | mqWithValidBreakpointsForRange(`width`)[methodName](value) 55 | ).toThrowMultiline(` 56 | [cssapi-mq] ${methodName}() Arguments included invalid value(s) 57 | – value: Value wasn't on the whitelist: ${toList(validValues)}`) 58 | })(invalidValues) 59 | }) 60 | 61 | map(value => { 62 | it(`returns the supplied ${name} for '${value}'`, () => { 63 | expect( 64 | mqWithValidBreakpointsForRange(`width`)[methodName](value) 65 | ).toMatchSnapshot() 66 | }) 67 | })(validValues) 68 | }) 69 | }) 70 | } 71 | 72 | map(feature => { 73 | const { name, validValues, allowNoArgument } = feature 74 | const { invalidValues } = featureValues(camelcase(name)) 75 | testLinearFeature(name, validValues, invalidValues, { allowNoArgument }) 76 | })(LINEAR_FEATURES) 77 | }) 78 | -------------------------------------------------------------------------------- /src/__tests__/api/tweakpoints.js: -------------------------------------------------------------------------------- 1 | import { map } from 'ramda' 2 | import cssSerialiser from '../testHelpers/cssSerialiser' 3 | import { 4 | mqWithValidBreakpointsForRange, 5 | mqWithTweakedBreakpointsForRange, 6 | } from '../testHelpers/data' 7 | 8 | expect.addSnapshotSerializer(cssSerialiser) 9 | 10 | describe(`tweak()`, () => { 11 | it(`throws if invalid breakpoint map is supplied`, () => { 12 | expect(() => 13 | mqWithValidBreakpointsForRange(`width`).tweak({ width: { small: `xxx` } }) 14 | ).toThrowMultiline(` 15 | [cssapi-mq] tweak() Arguments included invalid value(s) 16 | – tweakpoints: Object included invalid value(s) 17 | – width: Object included invalid value(s) 18 | – small: (Wasn't Valid Number and Wasn't Non-Negative) or Wasn't valid non-negative number with unit: 'rem' or Wasn't valid non-negative number with unit: 'em' or Wasn't valid non-negative number with unit: 'px'`) 19 | }) 20 | 21 | it(`throws if argument is invalid`, () => { 22 | const invalidTweakpoints = [``, true, false, `xxxx`, 444, []] 23 | 24 | map(value => { 25 | expect(() => mqWithValidBreakpointsForRange(`width`).tweak(value)) 26 | .toThrowMultiline(` 27 | [cssapi-mq] tweak() Arguments included invalid value(s) 28 | – tweakpoints: Wasn't Plain Object`) 29 | })(invalidTweakpoints) 30 | }) 31 | 32 | it(`doesn't throw with empty map`, () => { 33 | expect(() => 34 | mqWithValidBreakpointsForRange(`width`).tweak({}) 35 | ).not.toThrow() 36 | }) 37 | }) 38 | 39 | // ----------------------------------------------------------------------------- 40 | // Tweaked 41 | // ----------------------------------------------------------------------------- 42 | 43 | describe(`tweaked()`, () => { 44 | it(`throws when accessing original without an original object`, () => { 45 | expect(() => mqWithValidBreakpointsForRange(`width`).untweaked()) 46 | .toThrowMultiline(` 47 | [cssapi-mq] untweaked() There is no untweaked mq object available to untweak`) 48 | }) 49 | 50 | it(`includes original breakpoints and added tweakpoints`, () => { 51 | expect( 52 | mqWithTweakedBreakpointsForRange(`width`).aboveWidth(`alpha`) 53 | ).toMatchSnapshot() 54 | 55 | expect( 56 | mqWithTweakedBreakpointsForRange(`width`).betweenWidths(`alpha`, `xLarge`) 57 | ).toMatchSnapshot() 58 | }) 59 | 60 | describe(`doesn't effect the original mq`, () => { 61 | it(`tweaked breakpoints are not available`, () => { 62 | expect(() => 63 | mqWithTweakedBreakpointsForRange(`width`) 64 | .untweaked() 65 | .aboveWidth(`alpha`) 66 | ).toThrowMultiline(` 67 | [cssapi-mq] aboveWidth() There is no 'width' breakpoint defined called 'alpha', only: 'small', 'medium', 'large', 'xLarge' are defined`) 68 | }) 69 | 70 | it(`original breakpoints are available`, () => { 71 | expect( 72 | mqWithTweakedBreakpointsForRange(`width`) 73 | .untweaked() 74 | .atWidthBreakpoint(`small`) 75 | ).toMatchSnapshot() 76 | }) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/linearFeatures.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`linear features colorGamut() linear feature returns the supplied color-gamut for 'p3' 1`] = `(color-gamut: p3)`; 4 | 5 | exports[`linear features colorGamut() linear feature returns the supplied color-gamut for 'rec2020' 1`] = `(color-gamut: rec2020)`; 6 | 7 | exports[`linear features colorGamut() linear feature returns the supplied color-gamut for 'srgb' 1`] = `(color-gamut: srgb)`; 8 | 9 | exports[`linear features displayMode() linear feature returns the supplied display-mode for 'browser' 1`] = `(display-mode: browser)`; 10 | 11 | exports[`linear features displayMode() linear feature returns the supplied display-mode for 'fullscreen' 1`] = `(display-mode: fullscreen)`; 12 | 13 | exports[`linear features displayMode() linear feature returns the supplied display-mode for 'minimal-ui' 1`] = `(display-mode: minimal-ui)`; 14 | 15 | exports[`linear features displayMode() linear feature returns the supplied display-mode for 'standalone' 1`] = `(display-mode: standalone)`; 16 | 17 | exports[`linear features grid() linear feature returns a valueless grid 1`] = `(grid)`; 18 | 19 | exports[`linear features grid() linear feature returns the supplied grid for '0' 1`] = `(grid: 0)`; 20 | 21 | exports[`linear features grid() linear feature returns the supplied grid for '1' 1`] = `(grid: 1)`; 22 | 23 | exports[`linear features orientation() linear feature returns the supplied orientation for 'landscape' 1`] = `(orientation: landscape)`; 24 | 25 | exports[`linear features orientation() linear feature returns the supplied orientation for 'portrait' 1`] = `(orientation: portrait)`; 26 | 27 | exports[`linear features overflowBlock() linear feature returns the supplied overflow-block for 'none' 1`] = `(overflow-block: none)`; 28 | 29 | exports[`linear features overflowBlock() linear feature returns the supplied overflow-block for 'optional-paged' 1`] = `(overflow-block: optional-paged)`; 30 | 31 | exports[`linear features overflowBlock() linear feature returns the supplied overflow-block for 'scroll' 1`] = `(overflow-block: scroll)`; 32 | 33 | exports[`linear features overflowInline() linear feature returns the supplied overflow-inline for 'none' 1`] = `(overflow-inline: none)`; 34 | 35 | exports[`linear features overflowInline() linear feature returns the supplied overflow-inline for 'scroll' 1`] = `(overflow-inline: scroll)`; 36 | 37 | exports[`linear features scan() linear feature returns the supplied scan for 'interlace' 1`] = `(scan: interlace)`; 38 | 39 | exports[`linear features scan() linear feature returns the supplied scan for 'progressive' 1`] = `(scan: progressive)`; 40 | 41 | exports[`linear features update() linear feature returns a valueless update 1`] = `(update)`; 42 | 43 | exports[`linear features update() linear feature returns the supplied update for 'fast' 1`] = `(update: fast)`; 44 | 45 | exports[`linear features update() linear feature returns the supplied update for 'none' 1`] = `(update: none)`; 46 | 47 | exports[`linear features update() linear feature returns the supplied update for 'slow' 1`] = `(update: slow)`; 48 | -------------------------------------------------------------------------------- /src/features.js: -------------------------------------------------------------------------------- 1 | import validateIsNonNegativeValidInteger from './validations/validators/validateIsNonNegativeValidInteger' 2 | import validateIsDimension from './validations//validators/validateIsDimension' 3 | import validateIsResolution from './validations/validators/validateIsResolution' 4 | import validateIsAspectRatio from './validations/validators/validateIsAspectRatio' 5 | import dimensionTransformer from './validations/transformers/dimensionTransformer' 6 | import resolutionTransformer from './validations/transformers/resolutionTransformer' 7 | import identityTransformer from './validations/transformers/identityTransformer' 8 | import { LINEAR_FEATURE_NAMES, RANGED_FEATURE_NAMES } from './const' 9 | 10 | export const LINEAR_FEATURES = [ 11 | { 12 | name: LINEAR_FEATURE_NAMES.ORIENTATION, 13 | validValues: Object.freeze([`portrait`, `landscape`]), 14 | }, 15 | { 16 | name: LINEAR_FEATURE_NAMES.SCAN, 17 | validValues: Object.freeze([`interlace`, `progressive`]), 18 | }, 19 | { 20 | name: LINEAR_FEATURE_NAMES.GRID, 21 | validValues: Object.freeze([0, 1]), 22 | allowNoArgument: true, 23 | }, 24 | { 25 | name: LINEAR_FEATURE_NAMES.UPDATE, 26 | validValues: Object.freeze([`none`, `slow`, `fast`]), 27 | allowNoArgument: true, 28 | }, 29 | { 30 | name: LINEAR_FEATURE_NAMES.OVERFLOW_BLOCK, 31 | validValues: Object.freeze([`none`, `scroll`, `optional-paged`]), 32 | }, 33 | { 34 | name: LINEAR_FEATURE_NAMES.OVERFLOW_INLINE, 35 | validValues: Object.freeze([`none`, `scroll`]), 36 | }, 37 | { 38 | name: LINEAR_FEATURE_NAMES.COLOR_GAMUT, 39 | validValues: Object.freeze([`srgb`, `p3`, `rec2020`]), 40 | }, 41 | { 42 | name: LINEAR_FEATURE_NAMES.DISPLAY_MODE, 43 | validValues: Object.freeze([ 44 | `fullscreen`, 45 | `standalone`, 46 | `minimal-ui`, 47 | `browser`, 48 | ]), 49 | }, 50 | ] 51 | 52 | export const RANGED_FEATURES = [ 53 | { 54 | name: RANGED_FEATURE_NAMES.WIDTH, 55 | validator: validateIsDimension, 56 | transformer: dimensionTransformer, 57 | }, 58 | { 59 | name: RANGED_FEATURE_NAMES.HEIGHT, 60 | validator: validateIsDimension, 61 | transformer: dimensionTransformer, 62 | }, 63 | { 64 | name: RANGED_FEATURE_NAMES.RESOLUTION, 65 | validator: validateIsResolution, 66 | transformer: resolutionTransformer, 67 | }, 68 | { 69 | name: RANGED_FEATURE_NAMES.ASPECT_RATIO, 70 | validator: validateIsAspectRatio, 71 | transformer: identityTransformer, 72 | }, 73 | { 74 | name: RANGED_FEATURE_NAMES.COLOR, 75 | validator: validateIsNonNegativeValidInteger, 76 | transformer: identityTransformer, 77 | featureConfig: { 78 | allowNoArgument: true, 79 | }, 80 | }, 81 | { 82 | name: RANGED_FEATURE_NAMES.COLOR_INDEX, 83 | validator: validateIsNonNegativeValidInteger, 84 | transformer: identityTransformer, 85 | featureConfig: { 86 | allowNoArgument: true, 87 | }, 88 | }, 89 | { 90 | name: RANGED_FEATURE_NAMES.MONOCHROME, 91 | validator: validateIsNonNegativeValidInteger, 92 | transformer: identityTransformer, 93 | featureConfig: { 94 | allowNoArgument: true, 95 | }, 96 | }, 97 | ] 98 | -------------------------------------------------------------------------------- /src/errors2.js: -------------------------------------------------------------------------------- 1 | import { compose, construct } from 'ramda' 2 | import { configureRenderers } from 'folktale-validations' 3 | import { appendFlipped } from 'ramda-adjunct' 4 | import validatorMessages from './validations/validatorMessages' 5 | import { 6 | ERROR_PREFIX, 7 | CONFIGURE_PREFIX, 8 | API_MEDIA_TYPE_PREFIX, 9 | UNTWEAKED_PREFIX, 10 | functionPrefix, 11 | TWEAK_PREFIX, 12 | NOT_PREFIX, 13 | QUERY_PREFIX, 14 | } from './const' 15 | import { joinWithSpace } from './utils/string' 16 | 17 | const { argumentsFailureRenderer } = configureRenderers({ 18 | validatorMessages, 19 | }) 20 | 21 | // ----------------------------------------------------------------------------- 22 | // Utils 23 | // ----------------------------------------------------------------------------- 24 | 25 | const constructError = construct(Error) 26 | 27 | const throwError = error => { 28 | throw error 29 | } 30 | 31 | const throwNewError = compose(throwError, constructError) 32 | 33 | export const throwErrorWithMessage = compose( 34 | throwNewError, 35 | joinWithSpace, 36 | appendFlipped([ERROR_PREFIX]) 37 | ) 38 | 39 | export const throwErrorWithPrefixedMessage = prefix => 40 | compose(throwErrorWithMessage, joinWithSpace, appendFlipped([prefix])) 41 | 42 | // ----------------------------------------------------------------------------- 43 | // Prefixed Errors 44 | // ----------------------------------------------------------------------------- 45 | 46 | export const throwConfigureError = compose( 47 | throwErrorWithPrefixedMessage(CONFIGURE_PREFIX), 48 | argumentsFailureRenderer 49 | ) 50 | 51 | export const throwAPIMediaTypeError = compose( 52 | throwErrorWithPrefixedMessage(API_MEDIA_TYPE_PREFIX), 53 | argumentsFailureRenderer 54 | ) 55 | 56 | export const throwAPILinearFeatureError = name => value => 57 | compose( 58 | throwErrorWithPrefixedMessage(functionPrefix(name)), 59 | argumentsFailureRenderer 60 | )(value) 61 | 62 | export const throwAPIRangedFeatureError = name => value => 63 | compose( 64 | throwErrorWithPrefixedMessage(functionPrefix(name)), 65 | argumentsFailureRenderer 66 | )(value) 67 | 68 | export const throwAPITweakError = compose( 69 | throwErrorWithPrefixedMessage(TWEAK_PREFIX), 70 | argumentsFailureRenderer 71 | ) 72 | 73 | export const throwAPIUntweakedError = () => 74 | throwErrorWithPrefixedMessage(UNTWEAKED_PREFIX)( 75 | `There is no untweaked mq object available to untweak` 76 | ) 77 | 78 | export const throwAPINotError = compose( 79 | throwErrorWithPrefixedMessage(NOT_PREFIX), 80 | argumentsFailureRenderer 81 | ) 82 | 83 | export const throwAPIQueryError = compose( 84 | throwErrorWithPrefixedMessage(QUERY_PREFIX), 85 | argumentsFailureRenderer 86 | ) 87 | 88 | export const throwMissingBreakpointSetErrorMessage = breakpointMapName => 89 | throwNewError( 90 | `This mq object was not configured with a breakpoint set for '${breakpointMapName}'` 91 | ) 92 | 93 | export const throwScopedError = (scope, message) => 94 | throwErrorWithPrefixedMessage(functionPrefix(scope))(message) 95 | 96 | export const throwAPILinearFeatureInvalidValueError = compose( 97 | throwNewError, 98 | argumentsFailureRenderer 99 | ) 100 | -------------------------------------------------------------------------------- /src/__tests__/sharedTests/rangedFeatureHelpers.js: -------------------------------------------------------------------------------- 1 | import { compose, sequence, of, flip, repeat, filter, map } from 'ramda' 2 | 3 | import { 4 | mqWithValidBreakpointsForRange, 5 | validBreakpointKeysForRange, 6 | mqWithNoBreakpoints, 7 | } from '../testHelpers/data' 8 | 9 | const permutations = compose(sequence(of), flip(repeat)) 10 | const filterIfPairSame = filter(pair => pair[0] !== pair[1]) 11 | 12 | export const queryThrowsIfMissingBreakpoint = (name, method) => { 13 | it(`throws if breakpoint doesn't exist`, () => { 14 | expect(() => mqWithValidBreakpointsForRange(name)[method](`xxxx`)) 15 | .toThrowMultiline(` 16 | [cssapi-mq] ${method}() There is no '${name}' breakpoint defined called 'xxxx', only: 'small', 'medium', 'large', 'xLarge' are defined 17 | `) 18 | }) 19 | } 20 | 21 | export const queryThrowsIfMissingBreakpointSet = (name, method) => { 22 | it(`throws if breakpoint set doesn't exist`, () => { 23 | const mq = mqWithNoBreakpoints() 24 | expect(() => mq[method](`xxxx`)).toThrowMultiline(` 25 | [cssapi-mq] ${method}() This mq object was not configured with a breakpoint set for '${name}' 26 | `) 27 | }) 28 | } 29 | 30 | export const queryReturnsCorrectValueSingleBreakpoint = (name, method) => { 31 | map(breakpointName => { 32 | it(`returns the correct query for breakpoint '${breakpointName}'`, () => { 33 | const mq = mqWithValidBreakpointsForRange(name) 34 | const result = mq[method](breakpointName) 35 | expect(result).toMatchSnapshot() 36 | }) 37 | })(validBreakpointKeysForRange(name)) 38 | } 39 | 40 | export const queryReturnsCorrectValueWithTwoBreakpoints = (name, method) => { 41 | const possibleBreakpointCombinations = filterIfPairSame( 42 | permutations(2, validBreakpointKeysForRange(name)) 43 | ) 44 | map(breakpointNames => { 45 | it(`returns the correct query for breakpoints '${ 46 | breakpointNames[0] 47 | }' and '${breakpointNames[1]}'`, () => { 48 | const result = mqWithValidBreakpointsForRange(name)[method]( 49 | ...breakpointNames 50 | ) 51 | expect(result).toMatchSnapshot() 52 | }) 53 | })(possibleBreakpointCombinations) 54 | } 55 | 56 | export const queryThrowsWithBothBreakpointsTheSame = (name, method) => { 57 | it(`throws if 'from' and 'to' breakpoints are the same value`, () => { 58 | expect(() => mqWithValidBreakpointsForRange(name)[method](`large`, `large`)) 59 | .toThrowMultiline(` 60 | [cssapi-mq] ${method}() You must supply two different breakpoints but both were: 'large'. 61 | `) 62 | }) 63 | } 64 | 65 | export const queryThrowsIfMissingEitherBreakpoint = (name, method) => { 66 | it(`throws if 'from' breakpoint doesn't exist`, () => { 67 | expect(() => mqWithValidBreakpointsForRange(name)[method](`xxxx`, `large`)) 68 | .toThrowMultiline(` 69 | [cssapi-mq] ${method}() There is no '${name}' breakpoint defined called 'xxxx', only: 'small', 'medium', 'large', 'xLarge' are defined 70 | `) 71 | }) 72 | 73 | it(`throws if 'to' breakpoint doesn't exist`, () => { 74 | expect(() => mqWithValidBreakpointsForRange(name)[method](`large`, `xxxx`)) 75 | .toThrowMultiline(` 76 | [cssapi-mq] ${method}() There is no '${name}' breakpoint defined called 'xxxx', only: 'small', 'medium', 'large', 'xLarge' are defined`) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/featureValues.js: -------------------------------------------------------------------------------- 1 | import { 2 | junkValuesNotNull, 3 | genericValues, 4 | invalidValuesNotUndefined, 5 | invalidValues, 6 | genericStrings, 7 | genericPositiveIntegers, 8 | genericPositiveIntegersIncludingZero, 9 | genericNegativeIntegers, 10 | genericDecimals, 11 | positiveDimensionValues, 12 | genericResolutionValues, 13 | negativeResolutionValuesIncludingZero, 14 | positiveResolutionValues, 15 | genericAspectRatioValues, 16 | invalidAspectRatioValues, 17 | genericNegativeNumbers, 18 | genericNumbers, 19 | genericNegativeDecimals, 20 | } from './data' 21 | 22 | const dimensionsValues = { 23 | invalidNonExplicitValues: [...genericValues, ...positiveDimensionValues], 24 | invalidExplicitValues: [ 25 | ...invalidValues, 26 | ...genericStrings, 27 | ...genericNegativeDecimals, 28 | ...genericNegativeIntegers, 29 | ], 30 | validExplicitValues: [ 31 | ...genericPositiveIntegersIncludingZero, 32 | ...positiveDimensionValues, 33 | ], 34 | } 35 | 36 | const colorAndMonochromeValues = { 37 | invalidNonExplicitValues: [...genericValues], 38 | invalidExplicitValues: [ 39 | ...junkValuesNotNull, 40 | ...genericStrings, 41 | ...genericNegativeIntegers, 42 | ...genericDecimals, 43 | ], 44 | validExplicitValues: [...genericPositiveIntegersIncludingZero], 45 | } 46 | 47 | const featureValues = { 48 | // Linear 49 | orientation: { 50 | invalidValues: genericValues, 51 | }, 52 | scan: { 53 | invalidValues: genericValues, 54 | }, 55 | grid: { 56 | invalidValues: [ 57 | ...invalidValuesNotUndefined, 58 | ...genericStrings, 59 | ...genericNegativeNumbers, 60 | 2, 61 | ], 62 | }, 63 | update: { 64 | invalidValues: [ 65 | ...invalidValuesNotUndefined, 66 | ...genericStrings, 67 | ...genericNumbers, 68 | ], 69 | }, 70 | overflowBlock: { 71 | invalidValues: genericValues, 72 | }, 73 | overflowInline: { 74 | invalidValues: genericValues, 75 | }, 76 | colorGamut: { 77 | invalidValues: genericValues, 78 | }, 79 | displayMode: { 80 | invalidValues: genericValues, 81 | }, 82 | // Ranged 83 | width: { 84 | ...dimensionsValues, 85 | }, 86 | height: { 87 | ...dimensionsValues, 88 | }, 89 | resolution: { 90 | invalidNonExplicitValues: [...genericValues, ...genericResolutionValues], 91 | invalidExplicitValues: [ 92 | ...invalidValues, 93 | ...genericStrings, 94 | ...negativeResolutionValuesIncludingZero, 95 | ], 96 | validExplicitValues: [ 97 | ...positiveResolutionValues, 98 | ...genericPositiveIntegers, 99 | ], 100 | }, 101 | aspectRatio: { 102 | invalidNonExplicitValues: [ 103 | ...genericValues, 104 | ...genericAspectRatioValues, 105 | ...invalidAspectRatioValues, 106 | ], 107 | invalidExplicitValues: [...genericValues, ...invalidAspectRatioValues], 108 | validExplicitValues: [...genericAspectRatioValues], 109 | }, 110 | color: { 111 | ...colorAndMonochromeValues, 112 | }, 113 | colorIndex: { 114 | ...colorAndMonochromeValues, 115 | }, 116 | monochrome: { 117 | ...colorAndMonochromeValues, 118 | }, 119 | } 120 | 121 | export default featureName => featureValues[featureName] 122 | -------------------------------------------------------------------------------- /src/__tests__/api/__snapshots__/query.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`query and renders query with multiple features anded together 1`] = ` 4 | @media (color-gamut: srgb) and (min-width: 25em) and (orientation: landscape) and (grid) { 5 | background-color: function () { 6 | return 'GhostWhite'; 7 | }; ; 8 | } 9 | `; 10 | 11 | exports[`query and renders query with two features anded together 1`] = ` 12 | @media (min-width: 25em) and (orientation: landscape) { 13 | background-color: function () { 14 | return 'GhostWhite'; 15 | }; ; 16 | } 17 | `; 18 | 19 | exports[`query mixed allows mixed not queries (both and and or) 1`] = ` 20 | @media not screen and (display-mode: fullscreen) and (color-gamut: rec2020) and (grid: 0),(orientation: landscape) { 21 | background-color: function () { 22 | return 'GhostWhite'; 23 | }; ; 24 | } 25 | `; 26 | 27 | exports[`query mixed allows mixed queries (both and and or) 1`] = ` 28 | @media (display-mode: fullscreen),(color-gamut: rec2020),(display-mode: standalone) and (orientation: landscape) { 29 | background-color: function () { 30 | return 'GhostWhite'; 31 | }; ; 32 | } 33 | `; 34 | 35 | exports[`query mixed allows mixed queries and not queries (both and and or) 1`] = ` 36 | @media (grid),(color-gamut: rec2020),(grid: 1) and (orientation: landscape),not screen and (grid) and (width: 68.75em),(orientation: portrait) { 37 | background-color: function () { 38 | return 'GhostWhite'; 39 | }; ; 40 | } 41 | `; 42 | 43 | exports[`query not negates anded queries 1`] = ` 44 | @media not screen and (display-mode: fullscreen),(color-gamut: p3),(orientation: landscape) { 45 | background-color: function () { 46 | return 'GhostWhite'; 47 | }; ; 48 | } 49 | `; 50 | 51 | exports[`query not negates ored queries 1`] = ` 52 | @media not screen and (display-mode: fullscreen) and (color-gamut: p3) and (orientation: landscape) { 53 | background-color: function () { 54 | return 'GhostWhite'; 55 | }; ; 56 | } 57 | `; 58 | 59 | exports[`query not with media type negates anded queries without adding default media type 1`] = ` 60 | @media not screen and screen,(display-mode: fullscreen),(color-gamut: p3),(orientation: landscape) { 61 | background-color: function () { 62 | return 'GhostWhite'; 63 | }; ; 64 | } 65 | `; 66 | 67 | exports[`query not with media type negates ored queries without adding default media type 1`] = ` 68 | @media not screen and (display-mode: fullscreen) and (color-gamut: p3) and (orientation: landscape) { 69 | background-color: function () { 70 | return 'GhostWhite'; 71 | }; ; 72 | } 73 | `; 74 | 75 | exports[`query not with media type renders single uquery with media type without adding default media type 1`] = ` 76 | @media not screen and (display-mode: fullscreen) { 77 | background-color: function () { 78 | return 'GhostWhite'; 79 | }; ; 80 | } 81 | `; 82 | 83 | exports[`query or renders query with multiple features ored together 1`] = ` 84 | @media (color-gamut: srgb),(min-width: 25em),(orientation: landscape),(grid) { 85 | background-color: function () { 86 | return 'GhostWhite'; 87 | }; ; 88 | } 89 | `; 90 | 91 | exports[`query or renders query with two features ored together 1`] = ` 92 | @media (min-width: 25em),(orientation: landscape) { 93 | background-color: function () { 94 | return 'GhostWhite'; 95 | }; ; 96 | } 97 | `; 98 | 99 | exports[`query renders query with single feature 1`] = ` 100 | @media (min-width: 25em) { 101 | background-color: function () { 102 | return 'GhostWhite'; 103 | }; ; 104 | } 105 | `; 106 | -------------------------------------------------------------------------------- /src/__tests__/sharedTests/features.js: -------------------------------------------------------------------------------- 1 | import { reject, prepend, map } from 'ramda' 2 | import cssSerialiser from '../testHelpers/cssSerialiser' 3 | import { 4 | mqWithValidBreakpointsForRange, 5 | mqWithNoBreakpoints, 6 | invalidAspectRatioValues, 7 | } from '../testHelpers/data' 8 | 9 | expect.addSnapshotSerializer(cssSerialiser) 10 | 11 | const removeNull = reject(v => v === null) 12 | 13 | export const featureThrowsForInvalidBreakpoint = ( 14 | name, 15 | camelisedName, 16 | method, 17 | { invalidNonExplicitValues, allowNoArgument = false } = {} 18 | ) => { 19 | if (allowNoArgument) 20 | invalidNonExplicitValues = removeNull(invalidAspectRatioValues) 21 | 22 | it(`throws if supplied breakpoint value is invalid`, () => { 23 | map(value => { 24 | expect(() => mqWithValidBreakpointsForRange(camelisedName)[method](value)) 25 | .toThrowMultiline(` 26 | [cssapi-mq] ${method}() There is no '${name}' breakpoint defined called '${value}', only: 'small', 'medium', 'large', 'xLarge' are defined 27 | `) 28 | })(invalidNonExplicitValues) 29 | }) 30 | } 31 | 32 | export const featureThrowsForMissingArgument = ( 33 | name, 34 | camelizedName, 35 | method 36 | ) => { 37 | it(`throws if no argument is suppliedXßå`, () => { 38 | expect(() => mqWithValidBreakpointsForRange(camelizedName)[method]()) 39 | .toThrowMultiline(` 40 | [cssapi-mq] ${method}() There is no '${name}' breakpoint defined called 'undefined', only: 'small', 'medium', 'large', 'xLarge' are defined`) 41 | }) 42 | } 43 | 44 | export const featureThrowsForMissingBreakpointSet = ( 45 | name, 46 | camelizedName, 47 | method 48 | ) => { 49 | it(`throws if '${name}' breakpoint map doesn't exist`, () => { 50 | expect(() => mqWithNoBreakpoints()[method](`xxxx`)).toThrowMultiline(` 51 | [cssapi-mq] ${method}() This mq object was not configured with a breakpoint set for '${name}'`) 52 | }) 53 | } 54 | 55 | export const featureReturnsCorrectValueForBreakpoint = ( 56 | name, 57 | camelizedName, 58 | method 59 | ) => { 60 | it(`returns the correct feature when called with existing breakpoint`, () => { 61 | expect( 62 | mqWithValidBreakpointsForRange(camelizedName)[method](`small`) 63 | ).toMatchSnapshot() 64 | }) 65 | } 66 | 67 | export const featureThrowsForInvalidExplicitBreakpoint = ( 68 | name, 69 | camelizedName, 70 | method, 71 | { invalidExplicitValues, allowNoArgument = false } = {} 72 | ) => { 73 | if (allowNoArgument) 74 | invalidExplicitValues = removeNull(invalidAspectRatioValues) 75 | 76 | it(`throws if supplied explicit breakpoint value is invalid`, () => { 77 | map(value => { 78 | expect(() => mqWithValidBreakpointsForRange(camelizedName)[method](value)) 79 | .toThrowMultiline(` 80 | [cssapi-mq] ${method}() There is no '${name}' breakpoint defined called '${value}', only: 'small', 'medium', 'large', 'xLarge' are defined 81 | `) 82 | })(invalidExplicitValues) 83 | }) 84 | } 85 | 86 | export const featureReturnsCorrectValueForValidExpicitValue = ( 87 | name, 88 | camelizedName, 89 | method, 90 | { validExplicitValues, allowNoArgument = false } = {} 91 | ) => { 92 | if (allowNoArgument) 93 | validExplicitValues = prepend(undefined, validExplicitValues) 94 | map(value => { 95 | it(`returns the correct feature when called with a valid explicit value of '${value}'`, () => { 96 | expect( 97 | mqWithValidBreakpointsForRange(camelizedName, { 98 | useNamedBreakpoints: false, 99 | })[method](value) 100 | ).toMatchSnapshot() 101 | }) 102 | })(validExplicitValues) 103 | } 104 | 105 | export const featureReturnsCorrectValueForValidExpicitValueIncludeNull = ( 106 | name, 107 | camelizedName, 108 | method, 109 | { validExplicitValues } = {} 110 | ) => { 111 | featureReturnsCorrectValueForValidExpicitValue( 112 | name, 113 | method, 114 | prepend(null, validExplicitValues) 115 | ) 116 | } 117 | 118 | export const featureReturnsCorrectValueNoArguments = ( 119 | name, 120 | camelizedName, 121 | method 122 | ) => { 123 | it(`returns the correct feature when called with no arguments`, () => { 124 | expect( 125 | mqWithValidBreakpointsForRange(camelizedName)[method]() 126 | ).toMatchSnapshot() 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /src/features/buildRangedFeature.js: -------------------------------------------------------------------------------- 1 | import { 2 | findIndex, 3 | both, 4 | always, 5 | flip, 6 | toUpper, 7 | assoc, 8 | when, 9 | unless, 10 | pipe, 11 | } from 'ramda' 12 | import { isUndefined } from 'ramda-adjunct' 13 | import camelcase from 'camelcase' 14 | import { reduceObjIndexed } from '../utils/object' 15 | 16 | import { 17 | throwError, 18 | sameBreakpointsForBetweenErrorMessage, 19 | wrapWithErrorHandler, 20 | } from '../errors' 21 | 22 | import { 23 | getUpperLimit, 24 | propEqName, 25 | toBreakpointArray, 26 | getBreakpointNamed, 27 | } from '../utils/breakpoints' 28 | 29 | import { joinWithAnd } from '../utils/query' 30 | import { buildFeatureItem, validateAndTransform } from '../utils/features' 31 | 32 | export default ( 33 | featureName, 34 | validator, 35 | transformer, 36 | breakpointSet = {}, 37 | config 38 | ) => { 39 | const { defaultMediaType, useNamedBreakpoints, allowNoArgument } = config 40 | 41 | // --------------------------------------------------------------------------- 42 | // Renderer 43 | // --------------------------------------------------------------------------- 44 | 45 | const configuredValueRenderer = ( 46 | methodName, 47 | value, 48 | { canSeparateQueries = false, noArgs = false } = {} 49 | ) => 50 | unless( 51 | both(isUndefined, always(noArgs)), 52 | pipe( 53 | when( 54 | always(useNamedBreakpoints), 55 | getBreakpointNamed(featureName, methodName, breakpointSet) 56 | ), 57 | validateAndTransform( 58 | validator, 59 | transformer, 60 | assoc(`canSeparateQueries`, canSeparateQueries, config) 61 | ) 62 | ) 63 | )(value) 64 | 65 | const defaultAPIConfig = { mediaType: defaultMediaType } 66 | const orderedBreakpoints = toBreakpointArray(breakpointSet) 67 | const indexOfBreakpointNamed = flip(findIndex)(orderedBreakpoints) 68 | const nextBreakpointAboveNamed = getUpperLimit(orderedBreakpoints) 69 | 70 | // --------------------------------------------------------------------------- 71 | // Features 72 | // --------------------------------------------------------------------------- 73 | 74 | const feature = buildFeatureItem(featureName, configuredValueRenderer, { 75 | noArgs: allowNoArgument, 76 | }) 77 | const aboveFeature = buildFeatureItem( 78 | `min-${featureName}`, 79 | configuredValueRenderer 80 | ) 81 | const belowFeature = buildFeatureItem( 82 | `max-${featureName}`, 83 | configuredValueRenderer, 84 | { 85 | canSeparateQueries: true, 86 | } 87 | ) 88 | 89 | // --------------------------------------------------------------------------- 90 | // Feature Helpers 91 | // --------------------------------------------------------------------------- 92 | 93 | const betweenFeatures = (from, to) => { 94 | if (from === to) throwError(sameBreakpointsForBetweenErrorMessage(from)) 95 | const fromIndex = indexOfBreakpointNamed(propEqName(from)) 96 | const toIndex = indexOfBreakpointNamed(propEqName(to)) 97 | const [lower, higher] = fromIndex < toIndex ? [from, to] : [to, from] 98 | return joinWithAnd([aboveFeature(lower), belowFeature(higher)]) 99 | } 100 | 101 | const atFeatureBreakpoint = (breakpoint, conf = defaultAPIConfig) => { 102 | const breakpointAbove = nextBreakpointAboveNamed(breakpoint) 103 | return breakpointAbove 104 | ? betweenFeatures(breakpoint, breakpointAbove, conf) 105 | : aboveFeature(breakpoint, conf) 106 | } 107 | 108 | const titleizedName = 109 | toUpper(featureName[0]) + camelcase(featureName.slice(1)) 110 | 111 | // --------------------------------------------------------------------------- 112 | // Exports 113 | // --------------------------------------------------------------------------- 114 | 115 | const functionMap = { 116 | [camelcase(featureName)]: feature, 117 | [`min${titleizedName}`]: aboveFeature, 118 | [`max${titleizedName}`]: belowFeature, 119 | [`at${titleizedName}Breakpoint`]: atFeatureBreakpoint, 120 | [`between${titleizedName}s`]: betweenFeatures, 121 | [`at${titleizedName}`]: feature, 122 | [`above${titleizedName}`]: aboveFeature, 123 | [`below${titleizedName}`]: belowFeature, 124 | } 125 | 126 | return reduceObjIndexed( 127 | (acc, [functionName, f]) => 128 | assoc(functionName, wrapWithErrorHandler(functionName, f), acc), 129 | {} 130 | )(functionMap) 131 | } 132 | -------------------------------------------------------------------------------- /src/__tests__/rangedFeatures.js: -------------------------------------------------------------------------------- 1 | import { 2 | configSeparatesValuesWhenSet, 3 | configOutputsConfiguredDimensionUnits, 4 | } from './sharedTests/config' 5 | import { 6 | featureThrowsForMissingBreakpointSet, 7 | featureReturnsCorrectValueForBreakpoint, 8 | featureThrowsForMissingArgument, 9 | featureReturnsCorrectValueForValidExpicitValue, 10 | featureThrowsForInvalidExplicitBreakpoint, 11 | featureReturnsCorrectValueNoArguments, 12 | featureThrowsForInvalidBreakpoint, 13 | } from './sharedTests/features' 14 | import cssSerialiser from './testHelpers/cssSerialiser' 15 | import testRangedFeature from './testHelpers/testRangedFeature' 16 | import featureValues from './testHelpers/featureValues' 17 | 18 | expect.addSnapshotSerializer(cssSerialiser) 19 | 20 | // Tests common to features that support explicit values 21 | const explicitValueTests = [ 22 | featureReturnsCorrectValueForValidExpicitValue, 23 | featureThrowsForInvalidExplicitBreakpoint, 24 | ] 25 | 26 | // Tests common to all ranged features 27 | const sharedTests = { 28 | value: [ 29 | featureThrowsForMissingBreakpointSet, 30 | featureReturnsCorrectValueForBreakpoint, 31 | featureThrowsForInvalidBreakpoint, 32 | ...explicitValueTests, 33 | ], 34 | minValue: [ 35 | featureThrowsForMissingBreakpointSet, 36 | featureReturnsCorrectValueForBreakpoint, 37 | featureThrowsForMissingArgument, 38 | featureThrowsForInvalidBreakpoint, 39 | ...explicitValueTests, 40 | ], 41 | maxValue: [ 42 | featureThrowsForMissingBreakpointSet, 43 | featureReturnsCorrectValueForBreakpoint, 44 | featureThrowsForMissingArgument, 45 | featureThrowsForInvalidBreakpoint, 46 | ...explicitValueTests, 47 | ], 48 | } 49 | 50 | // Tests common to features that support separation of breakpoints 51 | const separationTests = [configSeparatesValuesWhenSet] 52 | 53 | describe(`ranged features`, () => { 54 | // Range 55 | testRangedFeature(`width`, { 56 | tests: { 57 | value: [ 58 | ...sharedTests.value, 59 | ...explicitValueTests, 60 | ...separationTests, 61 | configOutputsConfiguredDimensionUnits, 62 | featureThrowsForMissingArgument, 63 | ], 64 | minValue: [ 65 | ...sharedTests.minValue, 66 | ...explicitValueTests, 67 | ...separationTests, 68 | configOutputsConfiguredDimensionUnits, 69 | featureThrowsForMissingArgument, 70 | ], 71 | maxValue: [ 72 | ...sharedTests.maxValue, 73 | ...explicitValueTests, 74 | ...separationTests, 75 | configOutputsConfiguredDimensionUnits, 76 | featureThrowsForMissingArgument, 77 | ], 78 | }, 79 | ...featureValues(`width`), 80 | }) 81 | 82 | testRangedFeature(`height`, { 83 | tests: { 84 | value: [ 85 | ...sharedTests.value, 86 | ...explicitValueTests, 87 | ...separationTests, 88 | configOutputsConfiguredDimensionUnits, 89 | featureThrowsForMissingArgument, 90 | ], 91 | minValue: [ 92 | ...sharedTests.minValue, 93 | ...explicitValueTests, 94 | ...separationTests, 95 | configOutputsConfiguredDimensionUnits, 96 | featureThrowsForMissingArgument, 97 | ], 98 | maxValue: [ 99 | ...sharedTests.maxValue, 100 | ...explicitValueTests, 101 | ...separationTests, 102 | ...explicitValueTests, 103 | configOutputsConfiguredDimensionUnits, 104 | ], 105 | }, 106 | ...featureValues(`height`), 107 | }) 108 | 109 | testRangedFeature(`resolution`, { 110 | tests: { 111 | value: [ 112 | ...sharedTests.value, 113 | ...separationTests, 114 | ...explicitValueTests, 115 | featureThrowsForMissingArgument, 116 | ], 117 | minValue: [ 118 | ...sharedTests.minValue, 119 | ...separationTests, 120 | ...explicitValueTests, 121 | featureThrowsForMissingArgument, 122 | ], 123 | maxValue: [ 124 | ...sharedTests.maxValue, 125 | ...separationTests, 126 | ...explicitValueTests, 127 | featureThrowsForMissingArgument, 128 | ], 129 | }, 130 | ...featureValues(`resolution`), 131 | }) 132 | 133 | testRangedFeature(`aspect-ratio`, { 134 | tests: { 135 | value: [...sharedTests.value, featureThrowsForMissingArgument], 136 | minValue: [...sharedTests.minValue, featureThrowsForMissingArgument], 137 | maxValue: [...sharedTests.maxValue, featureThrowsForMissingArgument], 138 | }, 139 | ...featureValues(`aspectRatio`), 140 | }) 141 | 142 | testRangedFeature(`color`, { 143 | tests: { 144 | value: [...sharedTests.value, featureReturnsCorrectValueNoArguments], 145 | minValue: [...sharedTests.minValue, featureThrowsForMissingArgument], 146 | maxValue: [...sharedTests.maxValue, featureThrowsForMissingArgument], 147 | }, 148 | ...featureValues(`color`), 149 | allowNoArgument: true, 150 | }) 151 | 152 | testRangedFeature(`color-index`, { 153 | tests: { 154 | value: [...sharedTests.value, featureReturnsCorrectValueNoArguments], 155 | minValue: [...sharedTests.minValue, featureThrowsForMissingArgument], 156 | maxValue: [...sharedTests.maxValue, featureThrowsForMissingArgument], 157 | }, 158 | ...featureValues(`colorIndex`), 159 | allowNoArgument: true, 160 | }) 161 | 162 | testRangedFeature(`monochrome`, { 163 | tests: { 164 | value: [...sharedTests.value, featureReturnsCorrectValueNoArguments], 165 | minValue: [...sharedTests.minValue, featureThrowsForMissingArgument], 166 | maxValue: [...sharedTests.maxValue, featureThrowsForMissingArgument], 167 | }, 168 | ...featureValues(`monochrome`), 169 | allowNoArgument: true, 170 | }) 171 | }) 172 | -------------------------------------------------------------------------------- /src/__tests__/testHelpers/data.js: -------------------------------------------------------------------------------- 1 | import { keys } from 'ramda' 2 | import camelcase from 'camelcase' 3 | import styledMQ from '../../mq' 4 | 5 | // ----------------------------------------------------------------------------- 6 | // Values 7 | // ----------------------------------------------------------------------------- 8 | 9 | export const junkValuesNotNull = [undefined, NaN, ``, [], {}] 10 | export const junkValuesNotUndefined = [null, NaN, ``, [], {}] 11 | export const junkValues = [null, ...junkValuesNotNull] 12 | export const booleanValues = [true, false] 13 | export const invalidValues = [...junkValues, ...booleanValues] 14 | export const invalidValuesNotNull = [...junkValuesNotNull, ...booleanValues] 15 | export const invalidValuesNotUndefined = [ 16 | ...junkValuesNotUndefined, 17 | ...booleanValues, 18 | ] 19 | export const genericStrings = [`xxxx`] 20 | export const genericPositiveIntegers = [78] 21 | export const genericPositiveIntegersIncludingZero = [ 22 | 0, 23 | ...genericPositiveIntegers, 24 | ] 25 | export const genericNegativeIntegers = [-90] 26 | export const genericNegativeIntegersIncludingZero = [ 27 | 0, 28 | ...genericNegativeIntegers, 29 | ] 30 | export const genericPositiveDecimals = [44.7] 31 | export const genericPositiveDecimalsIncludingZero = [ 32 | 0, 33 | ...genericPositiveDecimals, 34 | ] 35 | export const genericNegativeDecimals = [-0.4] 36 | export const genericNegativeDecimalsIncludingZero = [ 37 | 0, 38 | ...genericNegativeDecimals, 39 | ] 40 | export const genericDecimals = [-99.8, 0.6] 41 | export const genericNegativeNumbers = [ 42 | ...genericNegativeDecimals, 43 | ...genericNegativeIntegers, 44 | ] 45 | export const genericPositiveNumbers = [ 46 | ...genericPositiveDecimals, 47 | ...genericPositiveIntegers, 48 | ] 49 | 50 | export const genericNegativeNumbersIncludingZero = [ 51 | 0, 52 | ...genericNegativeDecimals, 53 | ...genericNegativeIntegers, 54 | ] 55 | 56 | export const genericPositiveNumbersIncludingZero = [ 57 | 0, 58 | ...genericPositiveDecimalsIncludingZero, 59 | ...genericPositiveIntegersIncludingZero, 60 | ] 61 | export const positiveRemValues = [`163rem`, `555rem`] 62 | export const positiveEmValues = [`163em`, `555em`] 63 | export const positivePixelValues = [`163px`, `555px`] 64 | export const positiveDimensionValues = [ 65 | ...positiveEmValues, 66 | ...positiveRemValues, 67 | ...positivePixelValues, 68 | ] 69 | 70 | export const negativePixelValues = [`-163px`] 71 | export const negativeRemValuesOrZero = [`0rem`, `-163rem`] 72 | export const negativeEmValuesOrZero = [`0em`, `-163em`] 73 | export const negativePixelValuesOrZero = [`0px`, `-163px`] 74 | export const negativeDimensionValuesOrZero = [ 75 | ...negativeEmValuesOrZero, 76 | ...negativeRemValuesOrZero, 77 | ...negativePixelValuesOrZero, 78 | ] 79 | export const positiveResolutionValues = [`111dpi`] 80 | export const negativeResolutionValuesIncludingZero = [`0dpi`, `-163dpi`] 81 | export const genericAspectRatioValues = [ 82 | `16/9`, 83 | `1/1`, 84 | `6/4`, 85 | `16 / 9`, 86 | `1 / 1`, 87 | `6 / 4`, 88 | ] 89 | export const invalidAspectRatioValues = [ 90 | `0/9`, 91 | `1/0`, 92 | `0/0`, 93 | `-16/9`, 94 | `1/-1`, 95 | `-6/-`, 96 | ] 97 | 98 | export const genericResolutionValues = [ 99 | ...positiveResolutionValues, 100 | ...negativeResolutionValuesIncludingZero, 101 | ] 102 | 103 | export const genericNumbers = [ 104 | ...genericDecimals, 105 | ...genericNegativeIntegers, 106 | ...genericPositiveIntegersIncludingZero, 107 | ] 108 | 109 | export const genericValues = [ 110 | ...invalidValuesNotUndefined, 111 | ...genericStrings, 112 | ...genericNumbers, 113 | ] 114 | 115 | export const genericValuesNotNull = [ 116 | ...invalidValuesNotNull, 117 | ...genericStrings, 118 | ...genericNumbers, 119 | ] 120 | 121 | // ----------------------------------------------------------------------------- 122 | // Breakpoint Sets 123 | // ----------------------------------------------------------------------------- 124 | 125 | export const validDimensionBreakpoints = { 126 | small: 400, 127 | medium: `900px`, 128 | large: `68.75rem`, 129 | xLarge: `81.25em`, 130 | } 131 | 132 | export const validResolutionBreakpoints = { 133 | small: 72, 134 | medium: `150dpi`, 135 | large: 300, 136 | xLarge: `600dpi`, 137 | } 138 | 139 | export const validAspectRatioBreakpoints = { 140 | small: `2/3`, 141 | medium: `1/1`, 142 | large: `3/2`, 143 | xLarge: `16/9`, 144 | } 145 | 146 | export const validColorBreakpoints = { 147 | small: 1, 148 | medium: 4, 149 | large: 5, 150 | xLarge: 6, 151 | } 152 | 153 | export const validMonochromeBreakpoints = { 154 | small: 0, 155 | medium: 4, 156 | large: 8, 157 | xLarge: 16, 158 | } 159 | 160 | export const validBreakpointSets = { 161 | width: validDimensionBreakpoints, 162 | height: validDimensionBreakpoints, 163 | resolution: validResolutionBreakpoints, 164 | aspectRatio: validAspectRatioBreakpoints, 165 | color: validColorBreakpoints, 166 | colorIndex: validColorBreakpoints, 167 | monochrome: validMonochromeBreakpoints, 168 | } 169 | 170 | // ----------------------------------------------------------------------------- 171 | // Breakpoint Sets 172 | // ----------------------------------------------------------------------------- 173 | 174 | export const validBreakpointsForRange = name => { 175 | const camelisedName = camelcase(name) 176 | const o = {} 177 | o[camelisedName] = validBreakpointSets[camelisedName] 178 | return o 179 | } 180 | 181 | export const mqWithValidBreakpointsForRange = (name, config = {}) => 182 | styledMQ(validBreakpointsForRange(name), config) 183 | 184 | export const validBreakpointKeysForRange = name => { 185 | const camelisedName = camelcase(name) 186 | return keys(validBreakpointsForRange(name)[camelisedName]) 187 | } 188 | 189 | export const mqWithTweakedBreakpointsForRange = name => 190 | styledMQ(validBreakpointsForRange(name)).tweak({ width: { alpha: 300 } }) 191 | 192 | export const mqWithNoBreakpoints = () => styledMQ() 193 | -------------------------------------------------------------------------------- /src/constraints.js: -------------------------------------------------------------------------------- 1 | import { 2 | validateIsBoolean, 3 | validateIsPlainObject, 4 | validateIsArrayOf, 5 | validateIsNonEmptyArray, 6 | andValidator, 7 | validateArrayElements, 8 | validateIsWhitelistedValue, 9 | } from 'folktale-validations' 10 | import validateIsNumberOrPx from './validations/validators/validateIsNumberOrPx' 11 | import validateBreakpointMapNames from './validations/validators/validateBreakpointMapNames' 12 | import validateIsValidDimensionsUnit from './validations/validators/validateIsValidDimensionsUnit' 13 | import validateIsValidMediaType from './validations/validators/validateIsValidMediaType' 14 | import validateIsDimension from './validations/validators/validateIsDimension' 15 | import numberOrPxNumberToNumber from './validations/transformers/numberOrPxNumberToNumber' 16 | import { MEDIA_TYPES, UNITS } from './const' 17 | import validateIsResolution from './validations/validators/validateIsResolution' 18 | import validateIsAspectRatio from './validations/validators/validateIsAspectRatio' 19 | import validateIsQueryElement from './validations/validators/validateIsQueryElement' 20 | import validateIsNegationElement from './validations/validators/validateIsNegationElement' 21 | import validateIsNonNegativeValidInteger from './validations/validators/validateIsNonNegativeValidInteger' 22 | 23 | // ----------------------------------------------------------------------------- 24 | // Constraints for config obj 25 | // ----------------------------------------------------------------------------- 26 | 27 | export const CONFIG = { 28 | fields: [ 29 | { 30 | name: `baseFontSize`, 31 | validator: validateIsNumberOrPx, 32 | transformer: numberOrPxNumberToNumber, 33 | defaultValue: 16, 34 | }, 35 | { 36 | name: `defaultMediaType`, 37 | validator: validateIsValidMediaType, 38 | defaultValue: MEDIA_TYPES.SCREEN, 39 | }, 40 | { 41 | name: `dimensionsUnit`, 42 | validator: validateIsValidDimensionsUnit, 43 | defaultValue: UNITS.DIMENSIONS.EM, 44 | }, 45 | { 46 | name: `shouldSeparateQueries`, 47 | validator: validateIsBoolean, 48 | defaultValue: true, 49 | }, 50 | { 51 | name: `useNamedBreakpoints`, 52 | validator: validateIsBoolean, 53 | defaultValue: true, 54 | }, 55 | ], 56 | } 57 | 58 | export const BREAKPOINTS = { 59 | fieldsValidator: validateBreakpointMapNames, 60 | fields: [ 61 | { 62 | name: `width`, 63 | validator: validateIsPlainObject, 64 | value: { 65 | whitelistKeys: false, 66 | fields: [ 67 | { 68 | name: /.*/, 69 | validator: validateIsDimension, 70 | }, 71 | ], 72 | }, 73 | }, 74 | { 75 | name: `height`, 76 | validator: validateIsPlainObject, 77 | value: { 78 | whitelistKeys: false, 79 | fields: [ 80 | { 81 | name: /.*/, 82 | validator: validateIsDimension, 83 | }, 84 | ], 85 | }, 86 | }, 87 | { 88 | name: `resolution`, 89 | validator: validateIsPlainObject, 90 | value: { 91 | whitelistKeys: false, 92 | fields: [ 93 | { 94 | name: /.*/, 95 | validator: validateIsResolution, 96 | }, 97 | ], 98 | }, 99 | }, 100 | { 101 | name: `aspectRatio`, 102 | validator: validateIsPlainObject, 103 | value: { 104 | whitelistKeys: false, 105 | fields: [ 106 | { 107 | name: /.*/, 108 | validator: validateIsAspectRatio, 109 | }, 110 | ], 111 | }, 112 | }, 113 | { 114 | name: `color`, 115 | validator: validateIsPlainObject, 116 | value: { 117 | whitelistKeys: false, 118 | fields: [ 119 | { 120 | name: /.*/, 121 | validator: validateIsNonNegativeValidInteger, 122 | }, 123 | ], 124 | }, 125 | }, 126 | { 127 | name: `colorIndex`, 128 | validator: validateIsPlainObject, 129 | value: { 130 | whitelistKeys: false, 131 | fields: [ 132 | { 133 | name: /.*/, 134 | validator: validateIsNonNegativeValidInteger, 135 | }, 136 | ], 137 | }, 138 | }, 139 | { 140 | name: `monochrome`, 141 | validator: validateIsPlainObject, 142 | value: { 143 | whitelistKeys: false, 144 | fields: [ 145 | { 146 | name: /.*/, 147 | validator: validateIsNonNegativeValidInteger, 148 | }, 149 | ], 150 | }, 151 | }, 152 | ], 153 | } 154 | 155 | // ----------------------------------------------------------------------------- 156 | // Connfiguration 157 | // ----------------------------------------------------------------------------- 158 | 159 | export const MQ_ARGS = { 160 | fields: [ 161 | { 162 | name: `config`, 163 | validator: validateIsPlainObject, 164 | defaultValue: {}, 165 | value: CONFIG, 166 | }, 167 | { 168 | name: `breakpoints`, 169 | validator: validateIsPlainObject, 170 | defaultValue: {}, 171 | value: BREAKPOINTS, 172 | }, 173 | { 174 | name: `originalMQ`, 175 | validator: validateIsPlainObject, 176 | }, 177 | ], 178 | } 179 | 180 | // ----------------------------------------------------------------------------- 181 | // API 182 | // ----------------------------------------------------------------------------- 183 | 184 | export const API_MEDIA_TYPE = { 185 | fields: [ 186 | { 187 | name: `mediaTypes`, 188 | validator: validateIsArrayOf(validateIsValidMediaType), 189 | }, 190 | ], 191 | } 192 | 193 | export const API_TWEAK = { 194 | fields: [ 195 | { 196 | name: `tweakpoints`, 197 | validator: validateIsPlainObject, 198 | value: BREAKPOINTS, 199 | }, 200 | ], 201 | } 202 | 203 | export const API_QUERY = { 204 | fields: [ 205 | { 206 | name: `elements`, 207 | validator: andValidator( 208 | validateIsNonEmptyArray, 209 | validateArrayElements(validateIsQueryElement) 210 | ), 211 | isRequired: true, 212 | }, 213 | ], 214 | } 215 | 216 | export const API_NOT = { 217 | fields: [ 218 | { 219 | name: `elements`, 220 | validator: andValidator( 221 | validateIsNonEmptyArray, 222 | validateArrayElements(validateIsNegationElement) 223 | ), 224 | isRequired: true, 225 | }, 226 | ], 227 | } 228 | 229 | // ----------------------------------------------------------------------------- 230 | // Ranged Features 231 | // ----------------------------------------------------------------------------- 232 | 233 | export const constraintsForLinearFeature = ( 234 | validValues, 235 | isRequired = true 236 | ) => ({ 237 | fields: [ 238 | { 239 | name: `value`, 240 | validator: validateIsWhitelistedValue(validValues), 241 | isRequired, 242 | }, 243 | ], 244 | }) 245 | -------------------------------------------------------------------------------- /src/__tests__/configuration.js: -------------------------------------------------------------------------------- 1 | import { reject, compose, toString, map, range, zipObj } from 'ramda' 2 | import camelcase from 'camelcase' 3 | import styledMQ from '../mq' 4 | import cssSerialiser from './testHelpers/cssSerialiser' 5 | import { 6 | mqWithValidBreakpointsForRange, 7 | validBreakpointsForRange, 8 | mqWithNoBreakpoints, 9 | invalidValues, 10 | genericStrings, 11 | genericValues, 12 | junkValues, 13 | genericNumbers, 14 | genericPositiveNumbers, 15 | positivePixelValues, 16 | booleanValues, 17 | negativePixelValues, 18 | } from './testHelpers/data' 19 | import featureValues from './testHelpers/featureValues' 20 | import { rangedFeatureNames } from '../utils/features' 21 | 22 | expect.addSnapshotSerializer(cssSerialiser) 23 | 24 | const breakpointNames = compose(map(toString), range(1))(50) 25 | 26 | const validBreakpointValuesForFeature = name => ({ 27 | [name]: zipObj( 28 | breakpointNames, 29 | featureValues(camelcase(name)).validExplicitValues 30 | ), 31 | }) 32 | 33 | describe(`configure()`, () => { 34 | it(`doesn't throw with default configuration`, () => { 35 | expect(() => mqWithNoBreakpoints().not.toThrow()) 36 | }) 37 | 38 | describe(`breakpoints`, () => { 39 | it(`throws if invalid breakpoint set name is supplied`, () => { 40 | expect(() => styledMQ({ xxxx: { small: 100 } })).toThrowMultiline(` 41 | [cssapi-mq] configure() Arguments included invalid value(s) 42 | – breakpoints: Object included key(s) not on whitelist: ['width', 'height', 'resolution', 'aspectRatio', 'color', 'colorIndex', 'monochrome']`) 43 | }) 44 | 45 | const invalidBreakpointValues = [ 46 | ...genericStrings, 47 | ...booleanValues, 48 | ...genericNumbers, 49 | null, 50 | NaN, 51 | [], 52 | ] 53 | 54 | it(`throws if invalid value is supplied`, () => { 55 | map(value => { 56 | expect(() => styledMQ(value)).toThrowMultiline(` 57 | [cssapi-mq] configure() Arguments included invalid value(s) 58 | – breakpoints: Wasn't Plain Object`) 59 | })(invalidBreakpointValues) 60 | }) 61 | 62 | it(`doesn't throw if breakpoint set values are valid`, () => { 63 | map(rangedFeatureName => { 64 | const values = validBreakpointValuesForFeature(rangedFeatureName) 65 | expect(() => styledMQ(values)).not.toThrow() 66 | })(rangedFeatureNames) 67 | }) 68 | 69 | for (const featureName of rangedFeatureNames) { 70 | const invalidFeatureValues = featureValues(camelcase(featureName)) 71 | .invalidExplicitValues 72 | 73 | it(`throws if invalid '${featureName}' breakpoint set value is supplied`, () => { 74 | for (const invalidValue of invalidFeatureValues) { 75 | expect(() => 76 | styledMQ({ [featureName]: { a: invalidValue } }) 77 | ).toThrow(/^\[cssapi-mq\] configure\(\)/) 78 | } 79 | }) 80 | } 81 | }) 82 | 83 | describe(`config object`, () => { 84 | describe(`baseFontSize`, () => { 85 | it(`adjusts values based on 'basefontSize' for width`, () => { 86 | const result = mqWithValidBreakpointsForRange(`width`, { 87 | baseFontSize: 10, 88 | }).belowWidth(`small`) 89 | expect(result).toMatchSnapshot() 90 | }) 91 | 92 | it(`adjusts values based on 'basefontSize' for height`, () => { 93 | const result = mqWithValidBreakpointsForRange(`height`, { 94 | baseFontSize: 10, 95 | }).belowHeight(`small`) 96 | expect(result).toMatchSnapshot() 97 | }) 98 | 99 | const invalidBaseFontSizes = [...invalidValues, ...negativePixelValues] 100 | 101 | it(`throws if 'baseFontSize' is invalid`, () => { 102 | for (const value of invalidBaseFontSizes) { 103 | expect(() => 104 | styledMQ(validBreakpointsForRange(`width`), { 105 | baseFontSize: value, 106 | }) 107 | ).toThrowMultiline(` 108 | [cssapi-mq] configure() Arguments included invalid value(s) 109 | – config: Object included invalid value(s) 110 | – baseFontSize: (Wasn't Valid Number and Wasn't Non-Negative) or Wasn't valid non-negative number with unit: 'px'`) 111 | } 112 | }) 113 | 114 | const validBaseFontSizes = [ 115 | ...genericPositiveNumbers, 116 | ...positivePixelValues, 117 | ] 118 | 119 | it(`doesn't throw if 'baseFontSize' is invalid`, () => { 120 | for (const value of validBaseFontSizes) { 121 | expect(() => 122 | styledMQ(validBreakpointsForRange(`width`), { 123 | baseFontSize: value, 124 | }) 125 | ).not.toThrow() 126 | } 127 | }) 128 | }) 129 | 130 | describe(`defaultMediaType`, () => { 131 | const invalidDefaultMediaTypes = reject(v => v === null, genericValues) 132 | it(`throws if 'defaultMediaType' is invalid`, () => { 133 | for (const value of invalidDefaultMediaTypes) { 134 | expect(() => 135 | styledMQ(validBreakpointsForRange(`width`), { 136 | defaultMediaType: value, 137 | }) 138 | ).toThrowMultiline(` 139 | [cssapi-mq] configure() Arguments included invalid value(s) 140 | – config: Object included invalid value(s) 141 | – defaultMediaType: Value wasn't on the whitelist: ['all', 'print', 'screen', 'speech'] or Wasn't null`) 142 | } 143 | }) 144 | 145 | const validDefaultMediaTypes = [`screen`, `print`, `all`, `speech`, null] 146 | it(`doesn't throw if 'defaultMediaType' is invalid`, () => { 147 | for (const value of validDefaultMediaTypes) { 148 | expect(() => 149 | styledMQ(validBreakpointsForRange(`width`), { 150 | defaultMediaType: value, 151 | }) 152 | ).not.toThrow() 153 | } 154 | }) 155 | }) 156 | 157 | describe(`dimensionsUnit`, () => { 158 | const invalidDimensionsUnits = [...invalidValues, ...genericStrings] 159 | it(`throws if 'dimensionsUnit' is invalid`, () => { 160 | for (const value of invalidDimensionsUnits) { 161 | const config = { dimensionsUnit: value } 162 | expect(() => styledMQ(validBreakpointsForRange(`width`), config)) 163 | .toThrowMultiline(` 164 | [cssapi-mq] configure() Arguments included invalid value(s) 165 | – config: Object included invalid value(s) 166 | – dimensionsUnit: Value wasn't on the whitelist: ['em', 'rem', 'px'] 167 | `) 168 | } 169 | }) 170 | 171 | const validDimensionsUnits = [`em`, `rem`, `px`] 172 | it(`doesn't throw if 'dimensionsUnit' is invalid`, () => { 173 | for (const value of validDimensionsUnits) { 174 | const config = { dimensionsUnit: value } 175 | expect(() => 176 | styledMQ(validBreakpointsForRange(`width`), config) 177 | ).not.toThrow() 178 | } 179 | }) 180 | }) 181 | 182 | describe(`shouldSeparateQueries`, () => { 183 | const invalidShouldSeparateQueriesValues = [ 184 | ...junkValues, 185 | ...genericNumbers, 186 | genericStrings, 187 | ] 188 | 189 | it(`throws if 'shouldSeparateQueries' is invalid`, () => { 190 | for (const value of invalidShouldSeparateQueriesValues) { 191 | const config = { shouldSeparateQueries: value } 192 | expect(() => styledMQ(validBreakpointsForRange(`width`), config)) 193 | .toThrowMultiline(` 194 | [cssapi-mq] configure() Arguments included invalid value(s) 195 | – config: Object included invalid value(s) 196 | – shouldSeparateQueries: Wasn't Boolean`) 197 | } 198 | }) 199 | 200 | const validShouldSeparateQueriesValues = [true, false] 201 | it(`doesn't throw if 'shouldSeparateQueries' isn't valid'`, () => { 202 | for (const value of validShouldSeparateQueriesValues) { 203 | const config = { shouldSeparateQueries: value } 204 | expect(() => 205 | styledMQ(validBreakpointsForRange(`width`), config) 206 | ).not.toThrow() 207 | } 208 | }) 209 | }) 210 | }) 211 | }) 212 | -------------------------------------------------------------------------------- /src/__tests__/api/query.js: -------------------------------------------------------------------------------- 1 | import { map } from 'ramda' 2 | import { 3 | mqWithValidBreakpointsForRange, 4 | booleanValues, 5 | genericNumbers, 6 | } from '../testHelpers/data' 7 | import cssSerialiser from '../testHelpers/cssSerialiser' 8 | 9 | /* eslint-disable ramda/no-redundant-not */ 10 | 11 | expect.addSnapshotSerializer(cssSerialiser) 12 | 13 | describe(`query`, () => { 14 | it(`throws with no arguments`, () => { 15 | const mq = mqWithValidBreakpointsForRange(`width`) 16 | const { query } = mq 17 | expect( 18 | () => 19 | query()` 20 | background-color: ${() => `GhostWhite`}; 21 | ` 22 | ).toThrowMultiline(` 23 | [cssapi-mq] query() Arguments included invalid value(s) 24 | – elements: Was Empty Array 25 | `) 26 | }) 27 | 28 | const invalidValues = [ 29 | ...booleanValues, 30 | ...genericNumbers, 31 | null, 32 | undefined, 33 | NaN, 34 | {}, 35 | ] 36 | 37 | it(`throws with invalid argument`, () => { 38 | map(value => { 39 | const mq = mqWithValidBreakpointsForRange(`width`) 40 | const { query } = mq 41 | expect( 42 | () => 43 | query(value)` 44 | background-color: ${() => `GhostWhite`}; 45 | ` 46 | ).toThrowMultiline(` 47 | [cssapi-mq] query() Arguments included invalid value(s) 48 | – elements: Array included invalid value(s) 49 | – [0] Wasn't String or Wasn't Array or Wasn't negation (not) object 50 | `) 51 | })(invalidValues) 52 | }) 53 | 54 | it(`throws with invalid child`, () => { 55 | map(value => { 56 | const mq = mqWithValidBreakpointsForRange(`width`) 57 | const { query } = mq 58 | expect( 59 | () => 60 | query([value])` 61 | background-color: ${() => `GhostWhite`}; 62 | ` 63 | ).toThrowMultiline(` 64 | [cssapi-mq] query() Arguments included invalid value(s) 65 | – elements: Array included invalid value(s) 66 | – [0] Array included invalid value(s) 67 | – [0] Wasn't String`) 68 | })(invalidValues) 69 | }) 70 | 71 | it(`throws with a nested array`, () => { 72 | const mq = mqWithValidBreakpointsForRange(`width`) 73 | const { query } = mq 74 | expect( 75 | () => 76 | query([[]])` 77 | background-color: ${() => `GhostWhite`}; 78 | ` 79 | ).toThrowMultiline(` 80 | [cssapi-mq] query() Arguments included invalid value(s) 81 | – elements: Array included invalid value(s) 82 | – [0] Array included invalid value(s) 83 | – [0] Wasn't String`) 84 | }) 85 | 86 | it(`renders query with single feature`, () => { 87 | const mq = mqWithValidBreakpointsForRange(`width`) 88 | const { query, minWidth } = mq 89 | expect( 90 | query(minWidth(`small`))` 91 | background-color: ${() => `GhostWhite`}; 92 | ` 93 | ).toMatchSnapshot() 94 | }) 95 | 96 | describe(`and`, () => { 97 | // @media (min-width: 25em) and (orientation: landscape) { 98 | it(`renders query with two features anded together`, () => { 99 | const mq = mqWithValidBreakpointsForRange(`width`) 100 | const { query, minWidth, orientation } = mq 101 | expect( 102 | query([minWidth(`small`), orientation(`landscape`)])` 103 | background-color: ${() => `GhostWhite`}; 104 | ` 105 | ).toMatchSnapshot() 106 | }) 107 | 108 | it(`renders query with multiple features anded together`, () => { 109 | // @media screen and (min-width: 25em) and (orientation: landscape) and (grid) { 110 | const mq = mqWithValidBreakpointsForRange(`width`) 111 | const { query, colorGamut, grid, minWidth, orientation } = mq 112 | expect( 113 | query([ 114 | colorGamut(`srgb`), 115 | minWidth(`small`), 116 | orientation(`landscape`), 117 | grid(), 118 | ])` 119 | background-color: ${() => `GhostWhite`}; 120 | ` 121 | ).toMatchSnapshot() 122 | }) 123 | }) 124 | 125 | describe(`or`, () => { 126 | it(`renders query with two features ored together`, () => { 127 | // @media (min-width: 25em),(orientation: landscape) { 128 | const mq = mqWithValidBreakpointsForRange(`width`) 129 | const { query, minWidth, orientation } = mq 130 | expect( 131 | query(minWidth(`small`), orientation(`landscape`))` 132 | background-color: ${() => `GhostWhite`}; 133 | ` 134 | ).toMatchSnapshot() 135 | }) 136 | 137 | it(`renders query with multiple features ored together`, () => { 138 | // @media screen,(min-width: 25em),(orientation: landscape),(grid) { 139 | const mq = mqWithValidBreakpointsForRange(`width`) 140 | const { query, minWidth, orientation, colorGamut, grid } = mq 141 | expect( 142 | query( 143 | colorGamut(`srgb`), 144 | minWidth(`small`), 145 | orientation(`landscape`), 146 | grid() 147 | )` 148 | background-color: ${() => `GhostWhite`}; 149 | ` 150 | ).toMatchSnapshot() 151 | }) 152 | }) 153 | 154 | describe(`not`, () => { 155 | it(`throws with no arguments`, () => { 156 | const mq = mqWithValidBreakpointsForRange(`width`) 157 | const { query, not } = mq 158 | expect( 159 | () => 160 | query(not())` 161 | background-color: ${() => `GhostWhite`}; 162 | ` 163 | ).toThrowMultiline(` 164 | [cssapi-mq] not() Arguments included invalid value(s) 165 | – elements: Was Empty Array`) 166 | }) 167 | 168 | it(`throws with invalid argument`, () => { 169 | map(value => { 170 | const mq = mqWithValidBreakpointsForRange(`width`) 171 | const { query, not } = mq 172 | expect( 173 | () => 174 | query(not(value))` 175 | background-color: ${() => `GhostWhite`}; 176 | ` 177 | ).toThrowMultiline(` 178 | [cssapi-mq] not() Arguments included invalid value(s) 179 | – elements: Array included invalid value(s) 180 | – [0] Wasn't String or Wasn't Array`) 181 | })(invalidValues) 182 | }) 183 | 184 | it(`throws with not object`, () => { 185 | const mq = mqWithValidBreakpointsForRange(`width`) 186 | const { query, not } = mq 187 | expect( 188 | () => 189 | query(not({ not: `small` }))` 190 | background-color: ${() => `GhostWhite`}; 191 | ` 192 | ).toThrowMultiline(` 193 | [cssapi-mq] not() Arguments included invalid value(s) 194 | – elements: Array included invalid value(s) 195 | – [0] Wasn't String or Wasn't Array`) 196 | }) 197 | 198 | it(`throws with invalid child`, () => { 199 | map(value => { 200 | const mq = mqWithValidBreakpointsForRange(`width`) 201 | const { query, not } = mq 202 | expect( 203 | () => 204 | query(not([value]))` 205 | background-color: ${() => `GhostWhite`}; 206 | ` 207 | ).toThrowMultiline(` 208 | [cssapi-mq] not() Arguments included invalid value(s) 209 | – elements: Array included invalid value(s) 210 | – [0] Array included invalid value(s) 211 | – [0] Wasn't String`) 212 | })(invalidValues) 213 | }) 214 | 215 | it(`throws with a nested array`, () => { 216 | const mq = mqWithValidBreakpointsForRange(`width`) 217 | const { query, not } = mq 218 | expect( 219 | () => 220 | query(not([[]]))` 221 | background-color: ${() => `GhostWhite`}; 222 | ` 223 | ).toThrowMultiline(` 224 | [cssapi-mq] not() Arguments included invalid value(s) 225 | – elements: Array included invalid value(s) 226 | – [0] Array included invalid value(s) 227 | – [0] Wasn't String`) 228 | }) 229 | 230 | it(`negates anded queries`, () => { 231 | // @media not screen and (color) and (orientation: landscape) { 232 | const mq = mqWithValidBreakpointsForRange(`width`) 233 | const { displayMode, query, not, colorGamut, orientation } = mq 234 | expect( 235 | query( 236 | not([ 237 | displayMode(`fullscreen`), 238 | colorGamut(`p3`), 239 | orientation(`landscape`), 240 | ]) 241 | )` 242 | background-color: ${() => `GhostWhite`}; 243 | ` 244 | ).toMatchSnapshot() 245 | }) 246 | 247 | it(`negates ored queries`, () => { 248 | // @media not screen, not (color), not (orientation: landscape) { 249 | const mq = mqWithValidBreakpointsForRange(`width`) 250 | const { displayMode, query, not, colorGamut, orientation } = mq 251 | expect( 252 | query( 253 | not( 254 | displayMode(`fullscreen`), 255 | colorGamut(`p3`), 256 | orientation(`landscape`) 257 | ) 258 | )` 259 | background-color: ${() => `GhostWhite`}; 260 | ` 261 | ).toMatchSnapshot() 262 | }) 263 | 264 | describe(`with media type`, () => { 265 | it(`renders single uquery with media type without adding default media type`, () => { 266 | // @media (min-width: 25em) { 267 | const mq = mqWithValidBreakpointsForRange(`width`) 268 | const { query, not, displayMode, mediaType } = mq 269 | expect( 270 | query(not(mediaType(), displayMode(`fullscreen`)))` 271 | background-color: ${() => `GhostWhite`}; 272 | ` 273 | ).toMatchSnapshot() 274 | }) 275 | 276 | it(`negates anded queries without adding default media type`, () => { 277 | // @media not screen and (color) and (orientation: landscape) { 278 | const mq = mqWithValidBreakpointsForRange(`width`) 279 | const { 280 | mediaType, 281 | displayMode, 282 | query, 283 | not, 284 | colorGamut, 285 | orientation, 286 | } = mq 287 | expect( 288 | query( 289 | not([ 290 | mediaType(), 291 | displayMode(`fullscreen`), 292 | colorGamut(`p3`), 293 | orientation(`landscape`), 294 | ]) 295 | )` 296 | background-color: ${() => `GhostWhite`}; 297 | ` 298 | ).toMatchSnapshot() 299 | }) 300 | 301 | it(`negates ored queries without adding default media type`, () => { 302 | // @media not screen, not (color), not (orientation: landscape) { 303 | const mq = mqWithValidBreakpointsForRange(`width`) 304 | const { 305 | mediaType, 306 | displayMode, 307 | query, 308 | not, 309 | colorGamut, 310 | orientation, 311 | } = mq 312 | expect( 313 | query( 314 | not( 315 | mediaType(), 316 | displayMode(`fullscreen`), 317 | colorGamut(`p3`), 318 | orientation(`landscape`) 319 | ) 320 | )` 321 | background-color: ${() => `GhostWhite`}; 322 | ` 323 | ).toMatchSnapshot() 324 | }) 325 | }) 326 | }) 327 | 328 | describe(`mixed`, () => { 329 | it(`allows mixed queries (both and and or)`, () => { 330 | // @media screen, (color), screen and (color) and (orientation: landscape) { 331 | const mq = mqWithValidBreakpointsForRange(`width`) 332 | const { displayMode, query, colorGamut, orientation } = mq 333 | expect( 334 | query(displayMode(`fullscreen`), colorGamut(`rec2020`), [ 335 | displayMode(`standalone`), 336 | orientation(`landscape`), 337 | ])` 338 | background-color: ${() => `GhostWhite`}; 339 | ` 340 | ).toMatchSnapshot() 341 | }) 342 | 343 | it(`allows mixed not queries (both and and or)`, () => { 344 | // @media not screen, not (color), not screen and (color) and (orientation: landscape) { 345 | const mq = mqWithValidBreakpointsForRange(`width`) 346 | const { displayMode, query, not, colorGamut, orientation, grid } = mq 347 | expect( 348 | query( 349 | not(displayMode(`fullscreen`), colorGamut(`rec2020`), [ 350 | grid(0), 351 | orientation(`landscape`), 352 | ]) 353 | )` 354 | background-color: ${() => `GhostWhite`}; 355 | ` 356 | ).toMatchSnapshot() 357 | }) 358 | 359 | it(`allows mixed queries and not queries (both and and or)`, () => { 360 | // @media not screen, not (color), not screen and (color) and (orientation: landscape) { 361 | const mq = mqWithValidBreakpointsForRange(`width`) 362 | const { grid, atWidth, query, not, colorGamut, orientation } = mq 363 | expect( 364 | query( 365 | grid(), 366 | colorGamut(`rec2020`), 367 | [grid(1), orientation(`landscape`)], 368 | not(grid(), [atWidth(`large`), orientation(`portrait`)]) 369 | )` 370 | background-color: ${() => `GhostWhite`}; 371 | ` 372 | ).toMatchSnapshot() 373 | }) 374 | }) 375 | }) 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Toolkit For Consistent Management of Media Queries With Styled Components 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/cssapi-mq.svg)](https://www.npmjs.com/package/cssapi-mq) 4 | [![codecov](https://img.shields.io/codecov/c/github/Undistraction/cssapi-mq.svg)](https://codecov.io/gh/Undistraction/cssapi-mq) 5 | [![Build Status](https://img.shields.io/travis/Undistraction/cssapi-mq.svg)](https://travis-ci.org/Undistraction/cssapi-mq) 6 | [![DUB](https://img.shields.io/dub/l/vibe-d.svg)](./LICENSE.md) 7 | 8 | # CSSAPI MQ 9 | 10 | Note: This library used to be called Styled MQ. 11 | 12 | * The package is now named `cssapi-mq`. 13 | * The package now returns a function instead of an object with a `configuration` function, so you now pass configuration directly to this function: `mq(config) instead of`mq.configuration(config)` 14 | 15 | ## What? 16 | 17 | CSSAPI-MQ is a toolkit for dealing with media queries when using Styled 18 | Components. It offers a broad API for dealing with all widely supported media 19 | query features and can easily be used as is, or as the building blocks for your 20 | own more focused api. 21 | 22 | ## Why? 23 | 24 | I found other existing solutions didn't offer the power and flexibility I wanted 25 | and weren't well tested enough to give me confidence. 26 | 27 | ## How? 28 | 29 | It offers the following features: 30 | 31 | * Lots of validation and useful error messages to make it (hopefully) impossible 32 | to write invalid or non-functioning media queries. 33 | * Full support for all media features that are widely supported, including 34 | width, height, resolution, orientation and lots more. 35 | * A simple API with convenience methods like aboveResolution(), betweenWidths() 36 | and atHeight(). 37 | * Full support for complex media queries (ands, ors, not) with validation. 38 | * Support for tweakpoints. 39 | * Lots and lots of tests. 40 | * Automatic addition of media-type for not queries to ensure they are valid. 41 | * Work in whatever units you like (rems, ems or px) and choose your preferred 42 | output unit. 43 | * Use arbitrary values or make things strict, so only predefined, named 44 | breakpoints can be used. 45 | * Automatic separation of media query values to avoid overlap (if you want it). 46 | * Lots of scope for you to use as a robust base for using media queries however 47 | you want to - you can easily build out a few predefined queries if that's all 48 | you need or keep things very flexible. 49 | * Create multiple sets of configuration for different places within the same 50 | application. 51 | 52 | ## But I need … 53 | 54 | I've tried to cover everything realistic while ignoring legacy cruft. If there 55 | is anything you need beyond what is currently there please open an issue and 56 | explain what you need. 57 | 58 | ## Quick Start 59 | 60 | ```javascript 61 | import styled from 'styled-components' 62 | import mqAPI from 'cssapi-mq' 63 | 64 | // Define your mq object 65 | const mq = mqAPI({ 66 | width: { 67 | small: 400, 68 | medium: 900, 69 | large: 1200, 70 | xLarge: 1400, 71 | }, 72 | }) 73 | 74 | // Access the mq object 75 | 76 | const component = styled.div` 77 | ${mq.query(mq.betweenWidth('small', 'large'))` 78 | background-color: GhostWhite; 79 | `}; 80 | ` 81 | 82 | const component = styled.div` 83 | ${mq.aboveWidth('large')` 84 | background-color: WhiteSmoke; 85 | `}; 86 | ` 87 | ``` 88 | 89 | Or you can play with a Codepen [here](https://codepen.io/Pedr/pen/MOmpxr) 90 | 91 | ## A Quick Refresher On Media Queries 92 | 93 | Media queries can be broken down into two types - those that take a arbitrary 94 | value (which I will refer to as ranged queries), for example `width` or 95 | `resolution` and those that accept only values from a predefined list (which I 96 | will refer to as linear queries), for example `scan` which only accepts the 97 | values `interlace` or `progressive`. In some cases both ranged or linear queries 98 | also support no argument, for example `color` can be used with no argument to 99 | target only color devices. 100 | 101 | You can read much more about Media Queries 102 | [here](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries). 103 | 104 | ## Configuration 105 | 106 | You will configure your mq object with a call to `configure()`. Configure takes 107 | a breakpoint map as its first argument and a configuration object as its second. 108 | 109 | ### breakpoint map 110 | 111 | The breakpoint map allows you to define keyed breakpoints for any of the linear 112 | features, with a separate breakpoint set for each feature. If you wanted to 113 | defined breakpoints for `width`, `height` and `resolution`, your breakpoint map 114 | would look like this: 115 | 116 | ```javascript 117 | const mq = mqAPI({ 118 | width: { 119 | small: 400, 120 | medium: 900, 121 | large: 1200, 122 | xLarge: 1400, 123 | }, 124 | height: { 125 | small: 400, 126 | large: 800, 127 | }, 128 | resolution: { 129 | high: 192, 130 | }, 131 | }) 132 | ``` 133 | 134 | ### config object 135 | 136 | The second argument is a config object that changes the behaviour of the object 137 | that is retured. 138 | 139 | ```javascript 140 | const mq = mqAPI({ 141 | width: { 142 | small: 400, 143 | medium: 900, 144 | large: 1200, 145 | xLarge: 1400, 146 | }, 147 | { 148 | dimensionsUnit: 'rem', 149 | baseFontSize: 10, 150 | defaultMediaType: 'all', 151 | shouldSeparateQueries: false, 152 | useNamedBreakpoints: false, 153 | }); 154 | ``` 155 | 156 | * **dimensionsUnit em|rem|px** _(defaults to 'em')_ Define what value should be 157 | used when outputting dimensions units (`width` or `height`). 158 | * **baseFontSize** _(defaults to 16)_ If you choose to output dimensions in a 159 | relative unit (rems or ems, which are the default), then this will allow you 160 | to scale your relative units to match the scale you are using on your 161 | root-most document element. It is strongly advised you use ems. 162 | * **defaultMediaType all|print|screen|speech** _(defaults to 'screen')_ When 163 | using `not` in media queries, if a media type is missing the query will not be 164 | evaluated. CSSAPI-MQ will automatically add a media type to `not` queries if 165 | you don't define one yourself. This value will also decide the value what will 166 | be rendered if you call `mediaType()` with no argument. 167 | * **shouldSeparateQueries true|false** _(defaults to 'true')_ Using media 168 | queries that overlap is potentially problematic - if one media query uses an 169 | upper limit of 16em and another uses a lower limit of 16em, there is a 170 | potential conflict there which can be the source of bugs. If 171 | `shouldSeparateQueries` is set to `true`, CSSAPI-MQ will remove the smallest 172 | value possible from the upper breakpoints to avoid this collision. In the case 173 | of rems or ems this will be 0.01 less than the upper value. In the case of 174 | pixels it will be 1px. For this reason alone it is worth using ems. Note: this 175 | will only effect breakpoints defined in maps, not arbitrary values you pass in 176 | if `useNamedBreakpoints` is set ot `false`. 177 | * **useNamedBreakpoints true|false** _(defaults to 'true')_ By default 178 | StyledMQ will only allow you to pass in defined breakpoint names. If you try 179 | and pass in anything that isn't a key on the relevant breakpoint set you will 180 | receive an error. By setting this value to `false` you remove this validation 181 | and allow any valid value to be passed in. 182 | 183 | ### Validation 184 | 185 | CSSAPI-MQ takes a very strict approach to validations, whilst giving you as much 186 | freedom as possible in defining values. If you pass in an invalid value at any 187 | point it will throw an error and explain what the problem was. Given that you 188 | will be defining your media queries at author time, not runtime, this is of 189 | great benefit in allowing you to catch any issues immediatley. This hold true 190 | for the values you supply in your breakpoint map, or any values you supply to 191 | the methods exposed by CSSAPI-MQ> Principle causes of errors are: 192 | 193 | * You use an invalid value in a ranged query. 194 | * You use an invalid unit in a ranged query. 195 | * You use a negative value when only positive values make sense in a ranged 196 | query. 197 | * You use a decimal when only integers are valid in a ranged query. 198 | * You use a value that isn't in the list of valid values for a linear feature. 199 | * You use a breakpoint name that doesn't exist. 200 | * You defined a query that is invalid (for example through over nesting). 201 | 202 | ### Usage 203 | 204 | One you have defined your `mq` object, you can use it to create queries. The 205 | type of functions available to you depend on whether the feature is linear or 206 | ranged. In both cases you must wrap your features inside a call to query which 207 | will return a template literal function, ready for you to define the styles you 208 | want to apply within the query, for example: 209 | 210 | ```javascript 211 | mq.query(mq.mediaType('all'), mq.aboveWidth('medium'), mq.orientation('horizontal')` 212 | background-color: GhostWhite; 213 | `; 214 | ``` 215 | 216 | #### Media Type 217 | 218 | You can define the media type of a query or part of a query using `mediaType`: 219 | 220 | ```javascript 221 | mq.mediaType('print') 222 | ``` 223 | 224 | If it is called with no arguments, the configuration for `defaultMediaType` will 225 | be used: 226 | 227 | ```javascript 228 | mq.mediaType() 229 | ``` 230 | 231 | #### Linear 232 | 233 | * orientation 234 | * scan 235 | * grid 236 | * update 237 | * overflow-block 238 | * overflow-inline 239 | * color-gamut 240 | * display 241 | 242 | Linear features are accessible directly using the name of the feature: 243 | 244 | ```javascript 245 | mq.orientation('landscape') 246 | ``` 247 | 248 | Both `grid` and `update` support being called with no argument. If any other 249 | linear query is called without an argument it will throw. 250 | 251 | ```javascript 252 | mq.grid() 253 | mq.update() 254 | ``` 255 | 256 | #### Ranged 257 | 258 | * width 259 | * height 260 | * resolution 261 | * color 262 | * color-index 263 | * monochrome 264 | 265 | Ranged features each offer their own api using the same naming convention. The 266 | following takes `width` as an example, but the same is true for all ranged 267 | features. 268 | 269 | ##### `aboveWidth()` (aliased to `minWidth()`) 270 | 271 | ![aboveWidth() diagram](docs/images/aboveWidth.png?raw=true) 272 | 273 | Defines a `min-width` for the supplied breakpoint or value. 274 | 275 | ```javascript 276 | mq.aboveWidth('medium') 277 | mq.minWidth('medium') 278 | ``` 279 | 280 | ##### `belowWidth()` (aliased to `maxWidth()`) 281 | 282 | ![belowWidth() diagram](docs/images/belowWidth.png?raw=true) 283 | 284 | Defines a `min-width` for the supplied breakpoint or value. 285 | 286 | ```javascript 287 | mq.belowWidth('medium') 288 | mq.maxWidth('medium') 289 | ``` 290 | 291 | #### `atWidth()` (aliased to `width()`) 292 | 293 | ![atWidth() diagram](docs/images/atWidth.png?raw=true) 294 | 295 | Defines an exact width query for the supplied breakpoint or value. 296 | 297 | ```javascript 298 | mq.atWidth('medium') 299 | ``` 300 | 301 | #### `atBreakpointWidth()` 302 | 303 | ![atBreakpointWidth() diagram](docs/images/atBreakpointWidth.png?raw=true) 304 | 305 | Defines a range of values running from the supplied breakpoint or value to the 306 | next highest breakpoint, or without an upper limit if no higher value breakpoint 307 | exists. 308 | 309 | ```javascript 310 | mq.atBreakpointWidth('medium') 311 | ``` 312 | 313 | #### `betweenWidths()` 314 | 315 | ![betweenWidths() diagram](docs/images/betweenWidths.png?raw=true) 316 | 317 | Defines a range spanning the the values between the two supplied breakpoints or 318 | values. The order is not important with the lowest value always used for the 319 | lower end of the range. 320 | 321 | ```javascript 322 | mq.betweenWidth('medium', 'xLarge') 323 | ``` 324 | 325 | ### Building A Query 326 | 327 | All the previous methods output a string, however to generate a query from these 328 | strings you need to wrap them in a call to `query()`. How you assemble the 329 | values supplied to `query()` will effect how the media query is output. 330 | 331 | #### And 332 | 333 | Any items wrapped in an array are anded together, meaning all values in the 334 | anded group must be true for the query to succeed for example: 335 | 336 | ```javascript 337 | mq.query([mq.orientation('horizontal'), mq.aboveWidth('small')])`` 338 | ``` 339 | 340 | Query 341 | 342 | ```css 343 | @media ((orientation: horizontal) and (min-width: 25em)) { 344 | } 345 | ``` 346 | 347 | #### Or 348 | 349 | Each argument passed into `query()` is treated as an alternative, meaning any of 350 | the groups that have been ored together must be true for the query to succeed: 351 | 352 | ```javascript 353 | mq.query(mq.orientation('horizontal'), mq.aboveWidth('small'))`` 354 | ``` 355 | 356 | Query 357 | 358 | ```css 359 | @media ((orientation: horizontal), (min-width: 25em)) { 360 | } 361 | ``` 362 | 363 | #### Not 364 | 365 | Using `not()` allows you to negate a feature or set of features. Note that 366 | negated values must have a media type, and CSSAPI-MQ will add a media type if 367 | you don't do so yourself, using the value of the 'defaultMediaType` 368 | configuration. 369 | 370 | ```javascript 371 | mq.query(mq.not([mq.orientation('horizontal'), mq.aboveWidth('small')])``; 372 | ``` 373 | 374 | Query 375 | 376 | ```css 377 | @media (not (orientation: horizontal) and (min-width: 25em)) { 378 | } 379 | ``` 380 | 381 | Or 382 | 383 | ```javascript 384 | mq.query(mq.orientation('horizontal'), mq.aboveWidth('small'))`` 385 | ``` 386 | 387 | Query 388 | 389 | ```css 390 | @media (not screen and (orientation: horizontal), not screen and (min-width: 25em)) { 391 | } 392 | ``` 393 | 394 | ### Tweakpoints 395 | 396 | CSSAPI-MQ also supports the concept of tweakpoints - breakpoints that are 397 | specific to a component or small subset of components. In this case you can use 398 | the `tweak()` method to pass in additional maps of breakpoints. This will give 399 | you back a new tweaked `mq` obejct with all the same methods available, only now 400 | taking your newly defined tweakpoints into account. Your original `mq` object is 401 | still available via the `untweaked` property of the the tweaked `mq` object. 402 | With this approach you can pass the same original `mq` object to all your 403 | components and create a component-specific tweaked version as needed without 404 | effecting the original. 405 | 406 | ### Questions 407 | 408 | ### This is all so complicated. All I want is a few breakpoints. 409 | 410 | You can easily achieve what you want. If you are only intending to use a few 411 | clearly defined media queries in your app, just use StyledMQ to define them and 412 | store them as needed. For example lets say all you want is a small, medium and 413 | large breakpoint for your app width, all with the mediaType of screen. Just 414 | configure CSSAPI-MQ and store the query functions that it generated. 415 | 416 | ```javascript 417 | const mq = mqAPI({ 418 | width: { 419 | small: 400, 420 | medium: 900, 421 | large: 1200, 422 | }, 423 | }) 424 | 425 | const aboveSmallQuery = mq.query([mq.mediaType(), mq.aboveWidth('small')]) 426 | const aboveMediumQuery = mq.query([mq.mediaType(), mq.aboveWidth('medium')]) 427 | const abovelargeQuery = mq.query([mq.mediaType(), mq.aboveWidth('large')]) 428 | 429 | const queries = { 430 | aboveSmallQuery, 431 | aboveMediumQuery, 432 | aboveLargeQuery, 433 | } 434 | 435 | const component = styled.div` 436 | ${queries.aboveSmallQuery` 437 | background-color: GhostWhite; 438 | `}; 439 | ` 440 | ``` 441 | 442 | ### I don't like all this namespacing in the query definitions. 443 | 444 | You can just expand the `mq` object to a set of consts: 445 | 446 | ```javascript 447 | const { query, mediaType, aboveWidth } = mq 448 | 449 | const aboveSmallQuery = query([mediaType(), aboveWidth('small')]) 450 | ``` 451 | 452 | ### How should I use this on a real project. 453 | 454 | One possiblity is to create an `mq` object and make it available to all your 455 | components through a theme. 456 | 457 | ### I have two parts of the same app that need different sets of breakpoints. 458 | 459 | You can just create different `mq` objects for each part of the app. 460 | 461 | ## Maintainance 462 | 463 | ### Test 464 | 465 | ```sh 466 | yarn test 467 | ``` 468 | 469 | ### Build 470 | 471 | ```sh 472 | yarn run build 473 | ``` 474 | 475 | ### Publish 476 | 477 | ```sh 478 | yarn run publish:patch 479 | yarn run publish:minor 480 | yarn run publish:major 481 | ``` 482 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/rangedFeatures.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with a valid explicit value of '1 / 1' 1`] = `(aspect-ratio: 1 / 1)`; 4 | 5 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with a valid explicit value of '1/1' 1`] = `(aspect-ratio: 1/1)`; 6 | 7 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with a valid explicit value of '6 / 4' 1`] = `(aspect-ratio: 6 / 4)`; 8 | 9 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with a valid explicit value of '6/4' 1`] = `(aspect-ratio: 6/4)`; 10 | 11 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with a valid explicit value of '16 / 9' 1`] = `(aspect-ratio: 16 / 9)`; 12 | 13 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with a valid explicit value of '16/9' 1`] = `(aspect-ratio: 16/9)`; 14 | 15 | exports[`ranged features aspect-ratio range features aspectRatio() returns the correct feature when called with existing breakpoint 1`] = `(aspect-ratio: 2/3)`; 16 | 17 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with a valid explicit value of '1 / 1' 1`] = `(max-aspect-ratio: 1 / 1)`; 18 | 19 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with a valid explicit value of '1/1' 1`] = `(max-aspect-ratio: 1/1)`; 20 | 21 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with a valid explicit value of '6 / 4' 1`] = `(max-aspect-ratio: 6 / 4)`; 22 | 23 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with a valid explicit value of '6/4' 1`] = `(max-aspect-ratio: 6/4)`; 24 | 25 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with a valid explicit value of '16 / 9' 1`] = `(max-aspect-ratio: 16 / 9)`; 26 | 27 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with a valid explicit value of '16/9' 1`] = `(max-aspect-ratio: 16/9)`; 28 | 29 | exports[`ranged features aspect-ratio range features maxAspectRatio() returns the correct feature when called with existing breakpoint 1`] = `(max-aspect-ratio: 2/3)`; 30 | 31 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with a valid explicit value of '1 / 1' 1`] = `(min-aspect-ratio: 1 / 1)`; 32 | 33 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with a valid explicit value of '1/1' 1`] = `(min-aspect-ratio: 1/1)`; 34 | 35 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with a valid explicit value of '6 / 4' 1`] = `(min-aspect-ratio: 6 / 4)`; 36 | 37 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with a valid explicit value of '6/4' 1`] = `(min-aspect-ratio: 6/4)`; 38 | 39 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with a valid explicit value of '16 / 9' 1`] = `(min-aspect-ratio: 16 / 9)`; 40 | 41 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with a valid explicit value of '16/9' 1`] = `(min-aspect-ratio: 16/9)`; 42 | 43 | exports[`ranged features aspect-ratio range features minAspectRatio() returns the correct feature when called with existing breakpoint 1`] = `(min-aspect-ratio: 2/3)`; 44 | 45 | exports[`ranged features color range features color() returns the correct feature when called with a valid explicit value of '0' 1`] = `(color: 0)`; 46 | 47 | exports[`ranged features color range features color() returns the correct feature when called with a valid explicit value of '78' 1`] = `(color: 78)`; 48 | 49 | exports[`ranged features color range features color() returns the correct feature when called with a valid explicit value of 'undefined' 1`] = `(color)`; 50 | 51 | exports[`ranged features color range features color() returns the correct feature when called with existing breakpoint 1`] = `(color: 1)`; 52 | 53 | exports[`ranged features color range features color() returns the correct feature when called with no arguments 1`] = `(color)`; 54 | 55 | exports[`ranged features color range features maxColor() returns the correct feature when called with a valid explicit value of '0' 1`] = `(max-color: 0)`; 56 | 57 | exports[`ranged features color range features maxColor() returns the correct feature when called with a valid explicit value of '78' 1`] = `(max-color: 78)`; 58 | 59 | exports[`ranged features color range features maxColor() returns the correct feature when called with existing breakpoint 1`] = `(max-color: 1)`; 60 | 61 | exports[`ranged features color range features minColor() returns the correct feature when called with a valid explicit value of '0' 1`] = `(min-color: 0)`; 62 | 63 | exports[`ranged features color range features minColor() returns the correct feature when called with a valid explicit value of '78' 1`] = `(min-color: 78)`; 64 | 65 | exports[`ranged features color range features minColor() returns the correct feature when called with existing breakpoint 1`] = `(min-color: 1)`; 66 | 67 | exports[`ranged features color-index range features colorIndex() returns the correct feature when called with a valid explicit value of '0' 1`] = `(color-index: 0)`; 68 | 69 | exports[`ranged features color-index range features colorIndex() returns the correct feature when called with a valid explicit value of '78' 1`] = `(color-index: 78)`; 70 | 71 | exports[`ranged features color-index range features colorIndex() returns the correct feature when called with a valid explicit value of 'undefined' 1`] = `(color-index)`; 72 | 73 | exports[`ranged features color-index range features colorIndex() returns the correct feature when called with existing breakpoint 1`] = `(color-index: 1)`; 74 | 75 | exports[`ranged features color-index range features colorIndex() returns the correct feature when called with no arguments 1`] = `(color-index)`; 76 | 77 | exports[`ranged features color-index range features maxColorIndex() returns the correct feature when called with a valid explicit value of '0' 1`] = `(max-color-index: 0)`; 78 | 79 | exports[`ranged features color-index range features maxColorIndex() returns the correct feature when called with a valid explicit value of '78' 1`] = `(max-color-index: 78)`; 80 | 81 | exports[`ranged features color-index range features maxColorIndex() returns the correct feature when called with existing breakpoint 1`] = `(max-color-index: 1)`; 82 | 83 | exports[`ranged features color-index range features minColorIndex() returns the correct feature when called with a valid explicit value of '0' 1`] = `(min-color-index: 0)`; 84 | 85 | exports[`ranged features color-index range features minColorIndex() returns the correct feature when called with a valid explicit value of '78' 1`] = `(min-color-index: 78)`; 86 | 87 | exports[`ranged features color-index range features minColorIndex() returns the correct feature when called with existing breakpoint 1`] = `(min-color-index: 1)`; 88 | 89 | exports[`ranged features height range features height() doesn't separate values if not configured to do so 1`] = `(height: 25em)`; 90 | 91 | exports[`ranged features height range features height() renders configured dimensionsUnits 1`] = `(height: 25rem)`; 92 | 93 | exports[`ranged features height range features height() renders configured dimensionsUnits 2`] = `(height: 400px)`; 94 | 95 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '0' 1`] = `(height: 0em)`; 96 | 97 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '0' 2`] = `(height: 0em)`; 98 | 99 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '78' 1`] = `(height: 4.875em)`; 100 | 101 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '78' 2`] = `(height: 4.875em)`; 102 | 103 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '163em' 1`] = `(height: 163em)`; 104 | 105 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '163em' 2`] = `(height: 163em)`; 106 | 107 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '163px' 1`] = `(height: 10.1875em)`; 108 | 109 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '163px' 2`] = `(height: 10.1875em)`; 110 | 111 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '163rem' 1`] = `(height: 163em)`; 112 | 113 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '163rem' 2`] = `(height: 163em)`; 114 | 115 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '555em' 1`] = `(height: 555em)`; 116 | 117 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '555em' 2`] = `(height: 555em)`; 118 | 119 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '555px' 1`] = `(height: 34.6875em)`; 120 | 121 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '555px' 2`] = `(height: 34.6875em)`; 122 | 123 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '555rem' 1`] = `(height: 555em)`; 124 | 125 | exports[`ranged features height range features height() returns the correct feature when called with a valid explicit value of '555rem' 2`] = `(height: 555em)`; 126 | 127 | exports[`ranged features height range features height() returns the correct feature when called with existing breakpoint 1`] = `(height: 25em)`; 128 | 129 | exports[`ranged features height range features maxHeight() doesn't separate values if not configured to do so 1`] = `(height: 25em)`; 130 | 131 | exports[`ranged features height range features maxHeight() renders configured dimensionsUnits 1`] = `(height: 25rem)`; 132 | 133 | exports[`ranged features height range features maxHeight() renders configured dimensionsUnits 2`] = `(height: 400px)`; 134 | 135 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '0' 1`] = `(max-height: -0.000625em)`; 136 | 137 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '0' 2`] = `(max-height: -0.000625em)`; 138 | 139 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '0' 3`] = `(max-height: -0.000625em)`; 140 | 141 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '78' 1`] = `(max-height: 4.874375em)`; 142 | 143 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '78' 2`] = `(max-height: 4.874375em)`; 144 | 145 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '78' 3`] = `(max-height: 4.874375em)`; 146 | 147 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163em' 1`] = `(max-height: 162.999375em)`; 148 | 149 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163em' 2`] = `(max-height: 162.999375em)`; 150 | 151 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163em' 3`] = `(max-height: 162.999375em)`; 152 | 153 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163px' 1`] = `(max-height: 10.186875em)`; 154 | 155 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163px' 2`] = `(max-height: 10.186875em)`; 156 | 157 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163px' 3`] = `(max-height: 10.186875em)`; 158 | 159 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163rem' 1`] = `(max-height: 162.999375em)`; 160 | 161 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163rem' 2`] = `(max-height: 162.999375em)`; 162 | 163 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '163rem' 3`] = `(max-height: 162.999375em)`; 164 | 165 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555em' 1`] = `(max-height: 554.999375em)`; 166 | 167 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555em' 2`] = `(max-height: 554.999375em)`; 168 | 169 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555em' 3`] = `(max-height: 554.999375em)`; 170 | 171 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555px' 1`] = `(max-height: 34.686875em)`; 172 | 173 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555px' 2`] = `(max-height: 34.686875em)`; 174 | 175 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555px' 3`] = `(max-height: 34.686875em)`; 176 | 177 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555rem' 1`] = `(max-height: 554.999375em)`; 178 | 179 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555rem' 2`] = `(max-height: 554.999375em)`; 180 | 181 | exports[`ranged features height range features maxHeight() returns the correct feature when called with a valid explicit value of '555rem' 3`] = `(max-height: 554.999375em)`; 182 | 183 | exports[`ranged features height range features maxHeight() returns the correct feature when called with existing breakpoint 1`] = `(max-height: 24.999375em)`; 184 | 185 | exports[`ranged features height range features minHeight() doesn't separate values if not configured to do so 1`] = `(height: 25em)`; 186 | 187 | exports[`ranged features height range features minHeight() renders configured dimensionsUnits 1`] = `(height: 25rem)`; 188 | 189 | exports[`ranged features height range features minHeight() renders configured dimensionsUnits 2`] = `(height: 400px)`; 190 | 191 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '0' 1`] = `(min-height: 0em)`; 192 | 193 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '0' 2`] = `(min-height: 0em)`; 194 | 195 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '78' 1`] = `(min-height: 4.875em)`; 196 | 197 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '78' 2`] = `(min-height: 4.875em)`; 198 | 199 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '163em' 1`] = `(min-height: 163em)`; 200 | 201 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '163em' 2`] = `(min-height: 163em)`; 202 | 203 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '163px' 1`] = `(min-height: 10.1875em)`; 204 | 205 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '163px' 2`] = `(min-height: 10.1875em)`; 206 | 207 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '163rem' 1`] = `(min-height: 163em)`; 208 | 209 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '163rem' 2`] = `(min-height: 163em)`; 210 | 211 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '555em' 1`] = `(min-height: 555em)`; 212 | 213 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '555em' 2`] = `(min-height: 555em)`; 214 | 215 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '555px' 1`] = `(min-height: 34.6875em)`; 216 | 217 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '555px' 2`] = `(min-height: 34.6875em)`; 218 | 219 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '555rem' 1`] = `(min-height: 555em)`; 220 | 221 | exports[`ranged features height range features minHeight() returns the correct feature when called with a valid explicit value of '555rem' 2`] = `(min-height: 555em)`; 222 | 223 | exports[`ranged features height range features minHeight() returns the correct feature when called with existing breakpoint 1`] = `(min-height: 25em)`; 224 | 225 | exports[`ranged features monochrome range features maxMonochrome() returns the correct feature when called with a valid explicit value of '0' 1`] = `(max-monochrome: 0)`; 226 | 227 | exports[`ranged features monochrome range features maxMonochrome() returns the correct feature when called with a valid explicit value of '78' 1`] = `(max-monochrome: 78)`; 228 | 229 | exports[`ranged features monochrome range features maxMonochrome() returns the correct feature when called with existing breakpoint 1`] = `(max-monochrome: 0)`; 230 | 231 | exports[`ranged features monochrome range features minMonochrome() returns the correct feature when called with a valid explicit value of '0' 1`] = `(min-monochrome: 0)`; 232 | 233 | exports[`ranged features monochrome range features minMonochrome() returns the correct feature when called with a valid explicit value of '78' 1`] = `(min-monochrome: 78)`; 234 | 235 | exports[`ranged features monochrome range features minMonochrome() returns the correct feature when called with existing breakpoint 1`] = `(min-monochrome: 0)`; 236 | 237 | exports[`ranged features monochrome range features monochrome() returns the correct feature when called with a valid explicit value of '0' 1`] = `(monochrome: 0)`; 238 | 239 | exports[`ranged features monochrome range features monochrome() returns the correct feature when called with a valid explicit value of '78' 1`] = `(monochrome: 78)`; 240 | 241 | exports[`ranged features monochrome range features monochrome() returns the correct feature when called with a valid explicit value of 'undefined' 1`] = `(monochrome)`; 242 | 243 | exports[`ranged features monochrome range features monochrome() returns the correct feature when called with existing breakpoint 1`] = `(monochrome: 0)`; 244 | 245 | exports[`ranged features monochrome range features monochrome() returns the correct feature when called with no arguments 1`] = `(monochrome)`; 246 | 247 | exports[`ranged features resolution range features maxResolution() doesn't separate values if not configured to do so 1`] = `(resolution: 72dpi)`; 248 | 249 | exports[`ranged features resolution range features maxResolution() returns the correct feature when called with a valid explicit value of '78' 1`] = `(max-resolution: 77dpi)`; 250 | 251 | exports[`ranged features resolution range features maxResolution() returns the correct feature when called with a valid explicit value of '78' 2`] = `(max-resolution: 77dpi)`; 252 | 253 | exports[`ranged features resolution range features maxResolution() returns the correct feature when called with a valid explicit value of '111dpi' 1`] = `(max-resolution: 110dpi)`; 254 | 255 | exports[`ranged features resolution range features maxResolution() returns the correct feature when called with a valid explicit value of '111dpi' 2`] = `(max-resolution: 110dpi)`; 256 | 257 | exports[`ranged features resolution range features maxResolution() returns the correct feature when called with existing breakpoint 1`] = `(max-resolution: 71dpi)`; 258 | 259 | exports[`ranged features resolution range features minResolution() doesn't separate values if not configured to do so 1`] = `(resolution: 72dpi)`; 260 | 261 | exports[`ranged features resolution range features minResolution() returns the correct feature when called with a valid explicit value of '78' 1`] = `(min-resolution: 78dpi)`; 262 | 263 | exports[`ranged features resolution range features minResolution() returns the correct feature when called with a valid explicit value of '78' 2`] = `(min-resolution: 78dpi)`; 264 | 265 | exports[`ranged features resolution range features minResolution() returns the correct feature when called with a valid explicit value of '111dpi' 1`] = `(min-resolution: 111dpi)`; 266 | 267 | exports[`ranged features resolution range features minResolution() returns the correct feature when called with a valid explicit value of '111dpi' 2`] = `(min-resolution: 111dpi)`; 268 | 269 | exports[`ranged features resolution range features minResolution() returns the correct feature when called with existing breakpoint 1`] = `(min-resolution: 72dpi)`; 270 | 271 | exports[`ranged features resolution range features resolution() doesn't separate values if not configured to do so 1`] = `(resolution: 72dpi)`; 272 | 273 | exports[`ranged features resolution range features resolution() returns the correct feature when called with a valid explicit value of '78' 1`] = `(resolution: 78dpi)`; 274 | 275 | exports[`ranged features resolution range features resolution() returns the correct feature when called with a valid explicit value of '78' 2`] = `(resolution: 78dpi)`; 276 | 277 | exports[`ranged features resolution range features resolution() returns the correct feature when called with a valid explicit value of '111dpi' 1`] = `(resolution: 111dpi)`; 278 | 279 | exports[`ranged features resolution range features resolution() returns the correct feature when called with a valid explicit value of '111dpi' 2`] = `(resolution: 111dpi)`; 280 | 281 | exports[`ranged features resolution range features resolution() returns the correct feature when called with existing breakpoint 1`] = `(resolution: 72dpi)`; 282 | 283 | exports[`ranged features width range features maxWidth() doesn't separate values if not configured to do so 1`] = `(width: 25em)`; 284 | 285 | exports[`ranged features width range features maxWidth() renders configured dimensionsUnits 1`] = `(width: 25rem)`; 286 | 287 | exports[`ranged features width range features maxWidth() renders configured dimensionsUnits 2`] = `(width: 400px)`; 288 | 289 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '0' 1`] = `(max-width: -0.000625em)`; 290 | 291 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '0' 2`] = `(max-width: -0.000625em)`; 292 | 293 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '78' 1`] = `(max-width: 4.874375em)`; 294 | 295 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '78' 2`] = `(max-width: 4.874375em)`; 296 | 297 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '163em' 1`] = `(max-width: 162.999375em)`; 298 | 299 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '163em' 2`] = `(max-width: 162.999375em)`; 300 | 301 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '163px' 1`] = `(max-width: 10.186875em)`; 302 | 303 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '163px' 2`] = `(max-width: 10.186875em)`; 304 | 305 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '163rem' 1`] = `(max-width: 162.999375em)`; 306 | 307 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '163rem' 2`] = `(max-width: 162.999375em)`; 308 | 309 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '555em' 1`] = `(max-width: 554.999375em)`; 310 | 311 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '555em' 2`] = `(max-width: 554.999375em)`; 312 | 313 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '555px' 1`] = `(max-width: 34.686875em)`; 314 | 315 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '555px' 2`] = `(max-width: 34.686875em)`; 316 | 317 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '555rem' 1`] = `(max-width: 554.999375em)`; 318 | 319 | exports[`ranged features width range features maxWidth() returns the correct feature when called with a valid explicit value of '555rem' 2`] = `(max-width: 554.999375em)`; 320 | 321 | exports[`ranged features width range features maxWidth() returns the correct feature when called with existing breakpoint 1`] = `(max-width: 24.999375em)`; 322 | 323 | exports[`ranged features width range features minWidth() doesn't separate values if not configured to do so 1`] = `(width: 25em)`; 324 | 325 | exports[`ranged features width range features minWidth() renders configured dimensionsUnits 1`] = `(width: 25rem)`; 326 | 327 | exports[`ranged features width range features minWidth() renders configured dimensionsUnits 2`] = `(width: 400px)`; 328 | 329 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '0' 1`] = `(min-width: 0em)`; 330 | 331 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '0' 2`] = `(min-width: 0em)`; 332 | 333 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '78' 1`] = `(min-width: 4.875em)`; 334 | 335 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '78' 2`] = `(min-width: 4.875em)`; 336 | 337 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '163em' 1`] = `(min-width: 163em)`; 338 | 339 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '163em' 2`] = `(min-width: 163em)`; 340 | 341 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '163px' 1`] = `(min-width: 10.1875em)`; 342 | 343 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '163px' 2`] = `(min-width: 10.1875em)`; 344 | 345 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '163rem' 1`] = `(min-width: 163em)`; 346 | 347 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '163rem' 2`] = `(min-width: 163em)`; 348 | 349 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '555em' 1`] = `(min-width: 555em)`; 350 | 351 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '555em' 2`] = `(min-width: 555em)`; 352 | 353 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '555px' 1`] = `(min-width: 34.6875em)`; 354 | 355 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '555px' 2`] = `(min-width: 34.6875em)`; 356 | 357 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '555rem' 1`] = `(min-width: 555em)`; 358 | 359 | exports[`ranged features width range features minWidth() returns the correct feature when called with a valid explicit value of '555rem' 2`] = `(min-width: 555em)`; 360 | 361 | exports[`ranged features width range features minWidth() returns the correct feature when called with existing breakpoint 1`] = `(min-width: 25em)`; 362 | 363 | exports[`ranged features width range features width() doesn't separate values if not configured to do so 1`] = `(width: 25em)`; 364 | 365 | exports[`ranged features width range features width() renders configured dimensionsUnits 1`] = `(width: 25rem)`; 366 | 367 | exports[`ranged features width range features width() renders configured dimensionsUnits 2`] = `(width: 400px)`; 368 | 369 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '0' 1`] = `(width: 0em)`; 370 | 371 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '0' 2`] = `(width: 0em)`; 372 | 373 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '78' 1`] = `(width: 4.875em)`; 374 | 375 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '78' 2`] = `(width: 4.875em)`; 376 | 377 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '163em' 1`] = `(width: 163em)`; 378 | 379 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '163em' 2`] = `(width: 163em)`; 380 | 381 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '163px' 1`] = `(width: 10.1875em)`; 382 | 383 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '163px' 2`] = `(width: 10.1875em)`; 384 | 385 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '163rem' 1`] = `(width: 163em)`; 386 | 387 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '163rem' 2`] = `(width: 163em)`; 388 | 389 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '555em' 1`] = `(width: 555em)`; 390 | 391 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '555em' 2`] = `(width: 555em)`; 392 | 393 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '555px' 1`] = `(width: 34.6875em)`; 394 | 395 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '555px' 2`] = `(width: 34.6875em)`; 396 | 397 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '555rem' 1`] = `(width: 555em)`; 398 | 399 | exports[`ranged features width range features width() returns the correct feature when called with a valid explicit value of '555rem' 2`] = `(width: 555em)`; 400 | 401 | exports[`ranged features width range features width() returns the correct feature when called with existing breakpoint 1`] = `(width: 25em)`; 402 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/rangedFeatureHelpers.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ranged feature helpers aspect-ratio range queries aboveAspectRatio() returns the correct query for breakpoint 'large' 1`] = `(min-aspect-ratio: 3/2)`; 4 | 5 | exports[`ranged feature helpers aspect-ratio range queries aboveAspectRatio() returns the correct query for breakpoint 'medium' 1`] = `(min-aspect-ratio: 1/1)`; 6 | 7 | exports[`ranged feature helpers aspect-ratio range queries aboveAspectRatio() returns the correct query for breakpoint 'small' 1`] = `(min-aspect-ratio: 2/3)`; 8 | 9 | exports[`ranged feature helpers aspect-ratio range queries aboveAspectRatio() returns the correct query for breakpoint 'xLarge' 1`] = `(min-aspect-ratio: 16/9)`; 10 | 11 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatio() returns the correct query for breakpoint 'large' 1`] = `(aspect-ratio: 3/2)`; 12 | 13 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatio() returns the correct query for breakpoint 'medium' 1`] = `(aspect-ratio: 1/1)`; 14 | 15 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatio() returns the correct query for breakpoint 'small' 1`] = `(aspect-ratio: 2/3)`; 16 | 17 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatio() returns the correct query for breakpoint 'xLarge' 1`] = `(aspect-ratio: 16/9)`; 18 | 19 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatioBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-aspect-ratio: 3/2) and (max-aspect-ratio: 16/9)`; 20 | 21 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatioBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-aspect-ratio: 1/1) and (max-aspect-ratio: 3/2)`; 22 | 23 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatioBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 1/1)`; 24 | 25 | exports[`ranged feature helpers aspect-ratio range queries atAspectRatioBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-aspect-ratio: 16/9)`; 26 | 27 | exports[`ranged feature helpers aspect-ratio range queries belowAspectRatio() returns the correct query for breakpoint 'large' 1`] = `(max-aspect-ratio: 3/2)`; 28 | 29 | exports[`ranged feature helpers aspect-ratio range queries belowAspectRatio() returns the correct query for breakpoint 'medium' 1`] = `(max-aspect-ratio: 1/1)`; 30 | 31 | exports[`ranged feature helpers aspect-ratio range queries belowAspectRatio() returns the correct query for breakpoint 'small' 1`] = `(max-aspect-ratio: 2/3)`; 32 | 33 | exports[`ranged feature helpers aspect-ratio range queries belowAspectRatio() returns the correct query for breakpoint 'xLarge' 1`] = `(max-aspect-ratio: 16/9)`; 34 | 35 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-aspect-ratio: 1/1) and (max-aspect-ratio: 3/2)`; 36 | 37 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 3/2)`; 38 | 39 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-aspect-ratio: 3/2) and (max-aspect-ratio: 16/9)`; 40 | 41 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-aspect-ratio: 1/1) and (max-aspect-ratio: 3/2)`; 42 | 43 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 1/1)`; 44 | 45 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-aspect-ratio: 1/1) and (max-aspect-ratio: 16/9)`; 46 | 47 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 3/2)`; 48 | 49 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 1/1)`; 50 | 51 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 16/9)`; 52 | 53 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-aspect-ratio: 3/2) and (max-aspect-ratio: 16/9)`; 54 | 55 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-aspect-ratio: 1/1) and (max-aspect-ratio: 16/9)`; 56 | 57 | exports[`ranged feature helpers aspect-ratio range queries betweenAspectRatios() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-aspect-ratio: 2/3) and (max-aspect-ratio: 16/9)`; 58 | 59 | exports[`ranged feature helpers color range queries aboveColor() returns the correct query for breakpoint 'large' 1`] = `(min-color: 5)`; 60 | 61 | exports[`ranged feature helpers color range queries aboveColor() returns the correct query for breakpoint 'medium' 1`] = `(min-color: 4)`; 62 | 63 | exports[`ranged feature helpers color range queries aboveColor() returns the correct query for breakpoint 'small' 1`] = `(min-color: 1)`; 64 | 65 | exports[`ranged feature helpers color range queries aboveColor() returns the correct query for breakpoint 'xLarge' 1`] = `(min-color: 6)`; 66 | 67 | exports[`ranged feature helpers color range queries atColor() returns the correct query for breakpoint 'large' 1`] = `(color: 5)`; 68 | 69 | exports[`ranged feature helpers color range queries atColor() returns the correct query for breakpoint 'medium' 1`] = `(color: 4)`; 70 | 71 | exports[`ranged feature helpers color range queries atColor() returns the correct query for breakpoint 'small' 1`] = `(color: 1)`; 72 | 73 | exports[`ranged feature helpers color range queries atColor() returns the correct query for breakpoint 'xLarge' 1`] = `(color: 6)`; 74 | 75 | exports[`ranged feature helpers color range queries atColorBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-color: 5) and (max-color: 6)`; 76 | 77 | exports[`ranged feature helpers color range queries atColorBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-color: 4) and (max-color: 5)`; 78 | 79 | exports[`ranged feature helpers color range queries atColorBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-color: 1) and (max-color: 4)`; 80 | 81 | exports[`ranged feature helpers color range queries atColorBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-color: 6)`; 82 | 83 | exports[`ranged feature helpers color range queries belowColor() returns the correct query for breakpoint 'large' 1`] = `(max-color: 5)`; 84 | 85 | exports[`ranged feature helpers color range queries belowColor() returns the correct query for breakpoint 'medium' 1`] = `(max-color: 4)`; 86 | 87 | exports[`ranged feature helpers color range queries belowColor() returns the correct query for breakpoint 'small' 1`] = `(max-color: 1)`; 88 | 89 | exports[`ranged feature helpers color range queries belowColor() returns the correct query for breakpoint 'xLarge' 1`] = `(max-color: 6)`; 90 | 91 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-color: 4) and (max-color: 5)`; 92 | 93 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-color: 1) and (max-color: 5)`; 94 | 95 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-color: 5) and (max-color: 6)`; 96 | 97 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-color: 4) and (max-color: 5)`; 98 | 99 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-color: 1) and (max-color: 4)`; 100 | 101 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-color: 4) and (max-color: 6)`; 102 | 103 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-color: 1) and (max-color: 5)`; 104 | 105 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-color: 1) and (max-color: 4)`; 106 | 107 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-color: 1) and (max-color: 6)`; 108 | 109 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-color: 5) and (max-color: 6)`; 110 | 111 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-color: 4) and (max-color: 6)`; 112 | 113 | exports[`ranged feature helpers color range queries betweenColors() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-color: 1) and (max-color: 6)`; 114 | 115 | exports[`ranged feature helpers color-index range queries aboveColorIndex() returns the correct query for breakpoint 'large' 1`] = `(min-color-index: 5)`; 116 | 117 | exports[`ranged feature helpers color-index range queries aboveColorIndex() returns the correct query for breakpoint 'medium' 1`] = `(min-color-index: 4)`; 118 | 119 | exports[`ranged feature helpers color-index range queries aboveColorIndex() returns the correct query for breakpoint 'small' 1`] = `(min-color-index: 1)`; 120 | 121 | exports[`ranged feature helpers color-index range queries aboveColorIndex() returns the correct query for breakpoint 'xLarge' 1`] = `(min-color-index: 6)`; 122 | 123 | exports[`ranged feature helpers color-index range queries atColorIndex() returns the correct query for breakpoint 'large' 1`] = `(color-index: 5)`; 124 | 125 | exports[`ranged feature helpers color-index range queries atColorIndex() returns the correct query for breakpoint 'medium' 1`] = `(color-index: 4)`; 126 | 127 | exports[`ranged feature helpers color-index range queries atColorIndex() returns the correct query for breakpoint 'small' 1`] = `(color-index: 1)`; 128 | 129 | exports[`ranged feature helpers color-index range queries atColorIndex() returns the correct query for breakpoint 'xLarge' 1`] = `(color-index: 6)`; 130 | 131 | exports[`ranged feature helpers color-index range queries atColorIndexBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-color-index: 5) and (max-color-index: 6)`; 132 | 133 | exports[`ranged feature helpers color-index range queries atColorIndexBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-color-index: 4) and (max-color-index: 5)`; 134 | 135 | exports[`ranged feature helpers color-index range queries atColorIndexBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-color-index: 1) and (max-color-index: 4)`; 136 | 137 | exports[`ranged feature helpers color-index range queries atColorIndexBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-color-index: 6)`; 138 | 139 | exports[`ranged feature helpers color-index range queries belowColorIndex() returns the correct query for breakpoint 'large' 1`] = `(max-color-index: 5)`; 140 | 141 | exports[`ranged feature helpers color-index range queries belowColorIndex() returns the correct query for breakpoint 'medium' 1`] = `(max-color-index: 4)`; 142 | 143 | exports[`ranged feature helpers color-index range queries belowColorIndex() returns the correct query for breakpoint 'small' 1`] = `(max-color-index: 1)`; 144 | 145 | exports[`ranged feature helpers color-index range queries belowColorIndex() returns the correct query for breakpoint 'xLarge' 1`] = `(max-color-index: 6)`; 146 | 147 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-color-index: 4) and (max-color-index: 5)`; 148 | 149 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-color-index: 1) and (max-color-index: 5)`; 150 | 151 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-color-index: 5) and (max-color-index: 6)`; 152 | 153 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-color-index: 4) and (max-color-index: 5)`; 154 | 155 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-color-index: 1) and (max-color-index: 4)`; 156 | 157 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-color-index: 4) and (max-color-index: 6)`; 158 | 159 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-color-index: 1) and (max-color-index: 5)`; 160 | 161 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-color-index: 1) and (max-color-index: 4)`; 162 | 163 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-color-index: 1) and (max-color-index: 6)`; 164 | 165 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-color-index: 5) and (max-color-index: 6)`; 166 | 167 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-color-index: 4) and (max-color-index: 6)`; 168 | 169 | exports[`ranged feature helpers color-index range queries betweenColorIndexs() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-color-index: 1) and (max-color-index: 6)`; 170 | 171 | exports[`ranged feature helpers height range queries aboveHeight() returns the correct query for breakpoint 'large' 1`] = `(min-height: 68.75em)`; 172 | 173 | exports[`ranged feature helpers height range queries aboveHeight() returns the correct query for breakpoint 'medium' 1`] = `(min-height: 56.25em)`; 174 | 175 | exports[`ranged feature helpers height range queries aboveHeight() returns the correct query for breakpoint 'small' 1`] = `(min-height: 25em)`; 176 | 177 | exports[`ranged feature helpers height range queries aboveHeight() returns the correct query for breakpoint 'xLarge' 1`] = `(min-height: 81.25em)`; 178 | 179 | exports[`ranged feature helpers height range queries atHeight() returns the correct query for breakpoint 'large' 1`] = `(height: 68.75em)`; 180 | 181 | exports[`ranged feature helpers height range queries atHeight() returns the correct query for breakpoint 'medium' 1`] = `(height: 56.25em)`; 182 | 183 | exports[`ranged feature helpers height range queries atHeight() returns the correct query for breakpoint 'small' 1`] = `(height: 25em)`; 184 | 185 | exports[`ranged feature helpers height range queries atHeight() returns the correct query for breakpoint 'xLarge' 1`] = `(height: 81.25em)`; 186 | 187 | exports[`ranged feature helpers height range queries atHeightBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-height: 68.75em) and (max-height: 81.249375em)`; 188 | 189 | exports[`ranged feature helpers height range queries atHeightBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-height: 56.25em) and (max-height: 68.749375em)`; 190 | 191 | exports[`ranged feature helpers height range queries atHeightBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-height: 25em) and (max-height: 56.249375em)`; 192 | 193 | exports[`ranged feature helpers height range queries atHeightBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-height: 81.25em)`; 194 | 195 | exports[`ranged feature helpers height range queries belowHeight() returns the correct query for breakpoint 'large' 1`] = `(max-height: 68.749375em)`; 196 | 197 | exports[`ranged feature helpers height range queries belowHeight() returns the correct query for breakpoint 'medium' 1`] = `(max-height: 56.249375em)`; 198 | 199 | exports[`ranged feature helpers height range queries belowHeight() returns the correct query for breakpoint 'small' 1`] = `(max-height: 24.999375em)`; 200 | 201 | exports[`ranged feature helpers height range queries belowHeight() returns the correct query for breakpoint 'xLarge' 1`] = `(max-height: 81.249375em)`; 202 | 203 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-height: 56.25em) and (max-height: 68.749375em)`; 204 | 205 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-height: 25em) and (max-height: 68.749375em)`; 206 | 207 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-height: 68.75em) and (max-height: 81.249375em)`; 208 | 209 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-height: 56.25em) and (max-height: 68.749375em)`; 210 | 211 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-height: 25em) and (max-height: 56.249375em)`; 212 | 213 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-height: 56.25em) and (max-height: 81.249375em)`; 214 | 215 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-height: 25em) and (max-height: 68.749375em)`; 216 | 217 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-height: 25em) and (max-height: 56.249375em)`; 218 | 219 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-height: 25em) and (max-height: 81.249375em)`; 220 | 221 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-height: 68.75em) and (max-height: 81.249375em)`; 222 | 223 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-height: 56.25em) and (max-height: 81.249375em)`; 224 | 225 | exports[`ranged feature helpers height range queries betweenHeights() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-height: 25em) and (max-height: 81.249375em)`; 226 | 227 | exports[`ranged feature helpers monochrome range queries aboveMonochrome() returns the correct query for breakpoint 'large' 1`] = `(min-monochrome: 8)`; 228 | 229 | exports[`ranged feature helpers monochrome range queries aboveMonochrome() returns the correct query for breakpoint 'medium' 1`] = `(min-monochrome: 4)`; 230 | 231 | exports[`ranged feature helpers monochrome range queries aboveMonochrome() returns the correct query for breakpoint 'small' 1`] = `(min-monochrome: 0)`; 232 | 233 | exports[`ranged feature helpers monochrome range queries aboveMonochrome() returns the correct query for breakpoint 'xLarge' 1`] = `(min-monochrome: 16)`; 234 | 235 | exports[`ranged feature helpers monochrome range queries atMonochrome() returns the correct query for breakpoint 'large' 1`] = `(monochrome: 8)`; 236 | 237 | exports[`ranged feature helpers monochrome range queries atMonochrome() returns the correct query for breakpoint 'medium' 1`] = `(monochrome: 4)`; 238 | 239 | exports[`ranged feature helpers monochrome range queries atMonochrome() returns the correct query for breakpoint 'small' 1`] = `(monochrome: 0)`; 240 | 241 | exports[`ranged feature helpers monochrome range queries atMonochrome() returns the correct query for breakpoint 'xLarge' 1`] = `(monochrome: 16)`; 242 | 243 | exports[`ranged feature helpers monochrome range queries atMonochromeBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-monochrome: 8) and (max-monochrome: 16)`; 244 | 245 | exports[`ranged feature helpers monochrome range queries atMonochromeBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-monochrome: 4) and (max-monochrome: 8)`; 246 | 247 | exports[`ranged feature helpers monochrome range queries atMonochromeBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-monochrome: 0) and (max-monochrome: 4)`; 248 | 249 | exports[`ranged feature helpers monochrome range queries atMonochromeBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-monochrome: 16)`; 250 | 251 | exports[`ranged feature helpers monochrome range queries belowMonochrome() returns the correct query for breakpoint 'large' 1`] = `(max-monochrome: 8)`; 252 | 253 | exports[`ranged feature helpers monochrome range queries belowMonochrome() returns the correct query for breakpoint 'medium' 1`] = `(max-monochrome: 4)`; 254 | 255 | exports[`ranged feature helpers monochrome range queries belowMonochrome() returns the correct query for breakpoint 'small' 1`] = `(max-monochrome: 0)`; 256 | 257 | exports[`ranged feature helpers monochrome range queries belowMonochrome() returns the correct query for breakpoint 'xLarge' 1`] = `(max-monochrome: 16)`; 258 | 259 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-monochrome: 4) and (max-monochrome: 8)`; 260 | 261 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-monochrome: 0) and (max-monochrome: 8)`; 262 | 263 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-monochrome: 8) and (max-monochrome: 16)`; 264 | 265 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-monochrome: 4) and (max-monochrome: 8)`; 266 | 267 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-monochrome: 0) and (max-monochrome: 4)`; 268 | 269 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-monochrome: 4) and (max-monochrome: 16)`; 270 | 271 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-monochrome: 0) and (max-monochrome: 8)`; 272 | 273 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-monochrome: 0) and (max-monochrome: 4)`; 274 | 275 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-monochrome: 0) and (max-monochrome: 16)`; 276 | 277 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-monochrome: 8) and (max-monochrome: 16)`; 278 | 279 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-monochrome: 4) and (max-monochrome: 16)`; 280 | 281 | exports[`ranged feature helpers monochrome range queries betweenMonochromes() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-monochrome: 0) and (max-monochrome: 16)`; 282 | 283 | exports[`ranged feature helpers resolution range queries aboveResolution() returns the correct query for breakpoint 'large' 1`] = `(min-resolution: 300dpi)`; 284 | 285 | exports[`ranged feature helpers resolution range queries aboveResolution() returns the correct query for breakpoint 'medium' 1`] = `(min-resolution: 150dpi)`; 286 | 287 | exports[`ranged feature helpers resolution range queries aboveResolution() returns the correct query for breakpoint 'small' 1`] = `(min-resolution: 72dpi)`; 288 | 289 | exports[`ranged feature helpers resolution range queries aboveResolution() returns the correct query for breakpoint 'xLarge' 1`] = `(min-resolution: 600dpi)`; 290 | 291 | exports[`ranged feature helpers resolution range queries atResolution() returns the correct query for breakpoint 'large' 1`] = `(resolution: 300dpi)`; 292 | 293 | exports[`ranged feature helpers resolution range queries atResolution() returns the correct query for breakpoint 'medium' 1`] = `(resolution: 150dpi)`; 294 | 295 | exports[`ranged feature helpers resolution range queries atResolution() returns the correct query for breakpoint 'small' 1`] = `(resolution: 72dpi)`; 296 | 297 | exports[`ranged feature helpers resolution range queries atResolution() returns the correct query for breakpoint 'xLarge' 1`] = `(resolution: 600dpi)`; 298 | 299 | exports[`ranged feature helpers resolution range queries atResolutionBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-resolution: 300dpi) and (max-resolution: 599dpi)`; 300 | 301 | exports[`ranged feature helpers resolution range queries atResolutionBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-resolution: 150dpi) and (max-resolution: 299dpi)`; 302 | 303 | exports[`ranged feature helpers resolution range queries atResolutionBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-resolution: 72dpi) and (max-resolution: 149dpi)`; 304 | 305 | exports[`ranged feature helpers resolution range queries atResolutionBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-resolution: 600dpi)`; 306 | 307 | exports[`ranged feature helpers resolution range queries belowResolution() returns the correct query for breakpoint 'large' 1`] = `(max-resolution: 299dpi)`; 308 | 309 | exports[`ranged feature helpers resolution range queries belowResolution() returns the correct query for breakpoint 'medium' 1`] = `(max-resolution: 149dpi)`; 310 | 311 | exports[`ranged feature helpers resolution range queries belowResolution() returns the correct query for breakpoint 'small' 1`] = `(max-resolution: 71dpi)`; 312 | 313 | exports[`ranged feature helpers resolution range queries belowResolution() returns the correct query for breakpoint 'xLarge' 1`] = `(max-resolution: 599dpi)`; 314 | 315 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-resolution: 150dpi) and (max-resolution: 299dpi)`; 316 | 317 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-resolution: 72dpi) and (max-resolution: 299dpi)`; 318 | 319 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-resolution: 300dpi) and (max-resolution: 599dpi)`; 320 | 321 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-resolution: 150dpi) and (max-resolution: 299dpi)`; 322 | 323 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-resolution: 72dpi) and (max-resolution: 149dpi)`; 324 | 325 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-resolution: 150dpi) and (max-resolution: 599dpi)`; 326 | 327 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-resolution: 72dpi) and (max-resolution: 299dpi)`; 328 | 329 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-resolution: 72dpi) and (max-resolution: 149dpi)`; 330 | 331 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-resolution: 72dpi) and (max-resolution: 599dpi)`; 332 | 333 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-resolution: 300dpi) and (max-resolution: 599dpi)`; 334 | 335 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-resolution: 150dpi) and (max-resolution: 599dpi)`; 336 | 337 | exports[`ranged feature helpers resolution range queries betweenResolutions() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-resolution: 72dpi) and (max-resolution: 599dpi)`; 338 | 339 | exports[`ranged feature helpers width range queries aboveWidth() returns the correct query for breakpoint 'large' 1`] = `(min-width: 68.75em)`; 340 | 341 | exports[`ranged feature helpers width range queries aboveWidth() returns the correct query for breakpoint 'medium' 1`] = `(min-width: 56.25em)`; 342 | 343 | exports[`ranged feature helpers width range queries aboveWidth() returns the correct query for breakpoint 'small' 1`] = `(min-width: 25em)`; 344 | 345 | exports[`ranged feature helpers width range queries aboveWidth() returns the correct query for breakpoint 'xLarge' 1`] = `(min-width: 81.25em)`; 346 | 347 | exports[`ranged feature helpers width range queries atWidth() returns the correct query for breakpoint 'large' 1`] = `(width: 68.75em)`; 348 | 349 | exports[`ranged feature helpers width range queries atWidth() returns the correct query for breakpoint 'medium' 1`] = `(width: 56.25em)`; 350 | 351 | exports[`ranged feature helpers width range queries atWidth() returns the correct query for breakpoint 'small' 1`] = `(width: 25em)`; 352 | 353 | exports[`ranged feature helpers width range queries atWidth() returns the correct query for breakpoint 'xLarge' 1`] = `(width: 81.25em)`; 354 | 355 | exports[`ranged feature helpers width range queries atWidthBreakpoint() returns the correct query for breakpoint 'large' 1`] = `(min-width: 68.75em) and (max-width: 81.249375em)`; 356 | 357 | exports[`ranged feature helpers width range queries atWidthBreakpoint() returns the correct query for breakpoint 'medium' 1`] = `(min-width: 56.25em) and (max-width: 68.749375em)`; 358 | 359 | exports[`ranged feature helpers width range queries atWidthBreakpoint() returns the correct query for breakpoint 'small' 1`] = `(min-width: 25em) and (max-width: 56.249375em)`; 360 | 361 | exports[`ranged feature helpers width range queries atWidthBreakpoint() returns the correct query for breakpoint 'xLarge' 1`] = `(min-width: 81.25em)`; 362 | 363 | exports[`ranged feature helpers width range queries belowWidth() returns the correct query for breakpoint 'large' 1`] = `(max-width: 68.749375em)`; 364 | 365 | exports[`ranged feature helpers width range queries belowWidth() returns the correct query for breakpoint 'medium' 1`] = `(max-width: 56.249375em)`; 366 | 367 | exports[`ranged feature helpers width range queries belowWidth() returns the correct query for breakpoint 'small' 1`] = `(max-width: 24.999375em)`; 368 | 369 | exports[`ranged feature helpers width range queries belowWidth() returns the correct query for breakpoint 'xLarge' 1`] = `(max-width: 81.249375em)`; 370 | 371 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'large' and 'medium' 1`] = `(min-width: 56.25em) and (max-width: 68.749375em)`; 372 | 373 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'large' and 'small' 1`] = `(min-width: 25em) and (max-width: 68.749375em)`; 374 | 375 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'large' and 'xLarge' 1`] = `(min-width: 68.75em) and (max-width: 81.249375em)`; 376 | 377 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'medium' and 'large' 1`] = `(min-width: 56.25em) and (max-width: 68.749375em)`; 378 | 379 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'medium' and 'small' 1`] = `(min-width: 25em) and (max-width: 56.249375em)`; 380 | 381 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'medium' and 'xLarge' 1`] = `(min-width: 56.25em) and (max-width: 81.249375em)`; 382 | 383 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'small' and 'large' 1`] = `(min-width: 25em) and (max-width: 68.749375em)`; 384 | 385 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'small' and 'medium' 1`] = `(min-width: 25em) and (max-width: 56.249375em)`; 386 | 387 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'small' and 'xLarge' 1`] = `(min-width: 25em) and (max-width: 81.249375em)`; 388 | 389 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'xLarge' and 'large' 1`] = `(min-width: 68.75em) and (max-width: 81.249375em)`; 390 | 391 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'xLarge' and 'medium' 1`] = `(min-width: 56.25em) and (max-width: 81.249375em)`; 392 | 393 | exports[`ranged feature helpers width range queries betweenWidths() returns the correct query for breakpoints 'xLarge' and 'small' 1`] = `(min-width: 25em) and (max-width: 81.249375em)`; 394 | --------------------------------------------------------------------------------