diff --git a/package.json b/package.json index 42a1948..2c89d68 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "types": "lib/index.d.ts", "scripts": { "test": "tsc -p ./tsconfig.cjs.json --noEmit && jest", - "lint": "tslint --project tsconfig.base.json", + "lint": "tslint --project tsconfig.json", "format": "prettier --write \"src/**/*.ts\"", "test:watch": "jest --watch", "build": "rimraf ./lib && rimraf ./es && tsc -p ./tsconfig.cjs.json && tsc -p ./tsconfig.esm.json", diff --git a/src/__tests__/style.ts b/src/__tests__/style.ts index 10bf6f5..a7a1fd8 100644 --- a/src/__tests__/style.ts +++ b/src/__tests__/style.ts @@ -64,9 +64,7 @@ describe('style', () => { }); it('should return `undefined` if specified prop is not set', () => { - const result = style({ cssProp: 'output', prop: 'input' })({ - theme: {}, - }); + const result = style({ cssProp: 'output', prop: 'input' })({}); expect(result).toBe(undefined); }); @@ -74,7 +72,6 @@ describe('style', () => { it('should assign the value of `prop` to `cssProp` and return an object if a value is set', () => { const result = style({ cssProp: 'output', prop: 'input' })({ input: 'value', - theme: {}, }); expect(result).toEqual({ output: 'value' }); @@ -129,8 +126,8 @@ describe('style', () => { it('should return the respective values for breakpoints', () => { const result = style< IColorProps, - ITheme, - IBreakpoints + typeof themeWithBreakpoints, + typeof themeWithBreakpoints.breakpoints >({ cssProp: 'color', prop: 'input', @@ -160,8 +157,8 @@ describe('style', () => { it('should return the value as is for each breakpoint, if no value can be found on the theme', () => { const result = style< IColorProps<{ [index: string]: string }>, - ITheme, - IBreakpoints + typeof themeWithBreakpoints, + typeof themeWithBreakpoints.breakpoints >({ cssProp: 'color', prop: 'input', @@ -191,8 +188,8 @@ describe('style', () => { it('should order media query results based on ordering within `theme.breakpoints` not based on ordering of the responsive object props', () => { const result = style< IColorProps<{ [index: string]: string }>, - ITheme, - IBreakpoints + typeof themeWithBreakpoints, + typeof themeWithBreakpoints.breakpoints >({ cssProp: 'color', prop: 'input', @@ -218,8 +215,8 @@ describe('style', () => { it('should allow `base` as value for breakpoints to define the base value (without media query) of a property', () => { const result = style< IColorProps, - ITheme, - IBreakpoints + typeof themeWithBreakpoints, + typeof themeWithBreakpoints.breakpoints >({ cssProp: 'color', prop: 'input', @@ -251,8 +248,8 @@ describe('style', () => { it('should put base values in front of all other values', () => { const result = style< IColorProps, - ITheme, - IBreakpoints + typeof themeWithBreakpoints, + typeof themeWithBreakpoints.breakpoints >({ cssProp: 'color', prop: 'input', @@ -274,4 +271,39 @@ describe('style', () => { themeWithBreakpoints.breakpoints.large, ]); }); + + it('should return `undefined` if a responsive object is passed in but no theme and/or breakpoints are provided', () => { + const func = style< + IColorProps, + typeof themeWithBreakpoints, + typeof themeWithBreakpoints.breakpoints + >({ + cssProp: 'color', + prop: 'input', + themeProp: 'colors', + }); + + const noThemeResult = func({ + input: { + base: 'red', + large: 'red', + medium: 'green', + small: 'blue', + }, + theme: null as typeof themeWithBreakpoints, + }); + + const noBreakpointsResult = func({ + input: { + base: 'red', + large: 'red', + medium: 'green', + small: 'blue', + }, + theme: {} as typeof themeWithBreakpoints, + }); + + expect(noThemeResult).toBe(undefined); + expect(noBreakpointsResult).toBe(undefined); + }); }); diff --git a/src/style.ts b/src/style.ts index 146a288..7a2069a 100644 --- a/src/style.ts +++ b/src/style.ts @@ -1,14 +1,16 @@ import { + IDictionary, IStyleOptions, IStyles, ResponsivePropValue, WithTheme, } from './types'; -const BASE_EMPTY_OBJECT: { [index: string]: string } = {}; +const BASE_EMPTY_OBJECT = {}; +const BASE_EMPTY_INDEXED_OBJECT: IDictionary = BASE_EMPTY_OBJECT; const BREAKPOINTS_BASE_VALUE_KEY = 'base'; -export function style({ +export function style({ cssProp, prop, themeProp, @@ -20,9 +22,11 @@ export function style({ return undefined; } - const themeValue = themeProp - ? (props.theme[themeProp] as { [index: string]: any }) - : undefined; + const themeValue = + themeProp && props.theme + ? (props.theme[themeProp] as { [index: string]: any }) + : undefined; + const finalValue = themeValue && (typeof propValue === 'string' || typeof propValue === 'number') && @@ -34,11 +38,12 @@ export function style({ return { [cssProp]: finalValue, }; + } else if (!props.theme || !props.theme.breakpoints) { + return undefined; } else { const result: IStyles = {}; - const { theme } = props; - const { breakpoints = BASE_EMPTY_OBJECT } = theme; - const themeVal = themeValue || BASE_EMPTY_OBJECT; + const { breakpoints } = props.theme; + const themeVal = themeValue || BASE_EMPTY_INDEXED_OBJECT; // We rely on the fact that `Object.keys` enumerates keys in the way // they are added to an object. This should be consistent across engines. // In case that a bug is discovered we will have to switch to an implicit @@ -62,8 +67,9 @@ export function style({ if (key === BREAKPOINTS_BASE_VALUE_KEY) { acc[cssProp] = val; } else { - // TODO how to get around the cast to any? - const breakpointValue: string = breakpoints[key] as any; + const breakpointValue: string = (breakpoints as IDictionary)[ + key.toString() + ]; acc[breakpointValue] = { [cssProp]: val, }; diff --git a/src/types.ts b/src/types.ts index 433fe93..2ee0041 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,6 +20,10 @@ export type ResponsiveProp = [ ? ValueType : ValueType | ResponsivePropValue; +export interface IDictionary { + [index: string]: T; +} + export type ResponsiveObject = { [K in keyof P]?: ResponsiveProp }; @@ -33,7 +37,7 @@ export interface IBreakpointTheme { } export type ThemeWithBreakpoints = [B] extends [never] - ? ITheme + ? [T] extends [never] ? Partial> : ITheme : IBreakpointTheme; export type WithTheme = ResponsiveObject & diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 74089b3..81df269 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", "outDir": "lib" diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 8e2be10..b5cc877 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.json", "compilerOptions": { "module": "esnext", "outDir": "es" diff --git a/tsconfig.base.json b/tsconfig.json similarity index 100% rename from tsconfig.base.json rename to tsconfig.json