Skip to content

Commit

Permalink
Merge pull request #1002 from hasparus/better-ts-errors
Browse files Browse the repository at this point in the history
Friendlier TypeScript errors.
  • Loading branch information
hasparus authored Jun 30, 2020
2 parents 406bad7 + 0756514 commit c4a59c7
Show file tree
Hide file tree
Showing 15 changed files with 1,191 additions and 946 deletions.
29 changes: 16 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,41 @@
"packages/*"
],
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/helper-validator-identifier": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.8.3",
"@babel/runtime": "^7.7.7",
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/plugin-transform-runtime": "^7.10.1",
"@babel/preset-env": "^7.10.2",
"@babel/preset-react": "^7.10.1",
"@babel/preset-typescript": "^7.10.1",
"@babel/runtime": "^7.10.2",
"@testing-library/react": "^9.1.3",
"@types/jest": "^25.1.2",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.merge": "^4.6.6",
"@types/react-test-renderer": "^16.9.2",
"babel-jest": "^25.3.0",
"babel-jest": "^26.0.1",
"husky": ">=4.0.7",
"jest": "^24.8.0",
"jest": "^26.0.1",
"jest-canvas-mock": "^2.2.0",
"jest-emotion": "^10.0.11",
"jest-mock-console": "^1.0.0",
"jest-emotion": "^10.0.32",
"jest-mock-console": "^1.0.1",
"lerna": "^3.14.1",
"lint-staged": "10",
"microbundle": "^0.11.0",
"prettier": "^2.0.5",
"react-test-renderer": "^16.8.6",
"ts-jest": "^25.2.0"
"ts-jest": "^26.1.0",
"ts-snippet": "^4.2.0",
"typescript": "^3.9.5"
},
"resolutions": {},
"jest": {
"preset": "ts-jest/presets/js-with-babel",
"globals": {
"ts-jest": {
"tsConfig": {
"target": "es2018",
"module": "commonjs",
"esModuleInterop": true,
"resolveJsonModule": true,
Expand Down
15 changes: 10 additions & 5 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {
ThemeContext as EmotionContext,
InterpolationWithTheme,
} from '@emotion/core'
// @ts-ignore
import { css, Theme } from '@theme-ui/css'
import React from 'react'
import * as React from 'react'
import deepmerge from 'deepmerge'
import { version as __EMOTION_VERSION__ } from '@emotion/core/package.json'

Expand Down Expand Up @@ -72,8 +71,14 @@ const arrayMerge = (destinationArray, sourceArray, options) => sourceArray
export const merge = (a: Theme, b: Theme): Theme =>
deepmerge(a, b, { isMergeableObject, arrayMerge })

merge.all = <T = Theme>(...args: Partial<T>[]) =>
deepmerge.all<T>(args, { isMergeableObject, arrayMerge })
function mergeAll<A, B>(a: A, B: B): A & B
function mergeAll<A, B, C>(a: A, B: B, c: C): A & B & C
function mergeAll<A, B, C, D>(a: A, B: B, c: C, d: D): A & B & C & D
function mergeAll<T = Theme>(...args: Partial<T>[]) {
return deepmerge.all<T>(args, { isMergeableObject, arrayMerge })
}

merge.all = mergeAll

interface BaseProviderProps {
context: ContextValue
Expand Down Expand Up @@ -109,7 +114,7 @@ export function ThemeProvider({ theme, children }: ThemeProviderProps) {
const context =
typeof theme === 'function'
? { ...outer, theme: theme(outer.theme) }
: merge.all<ContextValue>({}, outer, { theme })
: merge.all({}, outer, { theme })

return jsx(BaseProvider, { context }, children)
}
48 changes: 30 additions & 18 deletions packages/core/test/index.js → packages/core/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import renderer from 'react-test-renderer'
import { render, fireEvent, cleanup, act } from '@testing-library/react'
import { matchers } from 'jest-emotion'
import mockConsole from 'jest-mock-console'
import { jsx, Context, useThemeUI, merge, ThemeProvider } from '../src'
import {
jsx,
Context,
useThemeUI,
merge,
ThemeProvider,
ContextValue,
} from '../src'

afterEach(cleanup)

expect.extend(matchers)

const renderJSON = (el) => renderer.create(el).toJSON()
const renderJSON = (el: React.ReactElement) => renderer.create(el).toJSON()

describe('ThemeProvider', () => {
test('renders', () => {
Expand All @@ -27,7 +34,8 @@ describe('ThemeProvider', () => {
const json = renderJSON(
<Context.Provider
value={{
emotionVersion: '9.0.0',
__EMOTION_VERSION__: '9.0.0',
theme: {},
}}>
<ThemeProvider theme={{}}>Conflicting versions</ThemeProvider>
</Context.Provider>
Expand Down Expand Up @@ -216,7 +224,7 @@ describe('jsx', () => {
test('does not add css prop when not provided', () => {
jest.spyOn(global.console, 'warn')
const json = renderJSON(jsx(React.Fragment, null, 'hi'))
expect(json.props).toEqual(undefined)
expect(json?.props).toEqual(undefined)
expect(console.warn).not.toBeCalled()
})
})
Expand All @@ -225,6 +233,7 @@ describe('merge', () => {
test('deeply merges objects', () => {
const result = merge(
{
// @ts-ignore
beep: 'boop',
hello: {
hi: 'howdy',
Expand Down Expand Up @@ -267,9 +276,12 @@ describe('merge', () => {
})

test('does not attempt to merge React components', () => {
const h1 = React.forwardRef((props, ref) => <h1 ref={ref} {...props} />)
const h1 = React.forwardRef<HTMLHeadingElement, {}>((props, ref) => (
<h1 ref={ref} {...props} />
))
const result = merge(
{
//@ts-ignore
h1: (props) => <h1 {...props} />,
},
{
Expand All @@ -282,53 +294,53 @@ describe('merge', () => {
test('primitive types override arrays', () => {
const result = merge(
{
fontSize: [3, 4, 5],
fontSizes: [3, 4, 5],
},
{
fontSize: 4,
fontSizes: 4 as any,
}
)
expect(result).toEqual({
fontSize: 4,
fontSizes: 4,
})
})

test('arrays override arrays', () => {
const result = merge(
{
fontSize: [3, 4, 5],
fontSizes: [3, 4, 5],
},
{
fontSize: [6, 7],
fontSizes: [6, 7],
}
)
expect(result).toEqual({
fontSize: [6, 7],
fontSizes: [6, 7],
})
})

test('arrays override primitive types', () => {
const result = merge(
{
fontSize: 5,
fontSizes: 5 as any,
},
{
fontSize: [6, 7],
fontSizes: [6, 7],
}
)
expect(result).toEqual({
fontSize: [6, 7],
fontSizes: [6, 7],
})
})
})

// describe('Context', () => {})
describe('useThemeUI', () => {
test('returns theme context', () => {
let context
const GetContext = (props) => {
let context: ContextValue | undefined
const GetContext = () => {
context = useThemeUI()
return false
return null
}
renderJSON(
<ThemeProvider
Expand All @@ -341,6 +353,6 @@ describe('useThemeUI', () => {
</ThemeProvider>
)
expect(context).toBeTruthy()
expect(context.theme.colors.text).toBe('tomato')
expect(context?.theme.colors?.text).toBe('tomato')
})
})
48 changes: 29 additions & 19 deletions packages/css/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { CSSObject, ThemeUIStyleObject, UseThemeFunction, Theme } from './types'
import {
CSSObject,
ThemeUIStyleObject,
ThemeDerivedStyles,
Theme,
ThemeUICSSObject,
} from './types'

export * from './types'

export function get(
obj: object,
key: string | number,
key: string | number | undefined,
def?: unknown,
p?: number,
undef?: unknown
): any {
const path = key && typeof key === 'string' ? key.split('.') : [key]
for (p = 0; p < path.length; p++) {
obj = obj ? (obj as any)[path[p]] : undef
obj = obj ? (obj as any)[path[p]!] : undef
}
return obj === undef ? def : obj
}

export const defaultBreakpoints = [40, 52, 64].map(n => n + 'em')
export const defaultBreakpoints = [40, 52, 64].map((n) => n + 'em')

const defaultTheme = {
space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
Expand Down Expand Up @@ -215,20 +221,23 @@ const transforms = [
{}
)

const responsive = (styles: Exclude<ThemeUIStyleObject, UseThemeFunction>) => (
theme?: Theme
) => {
const next: Exclude<ThemeUIStyleObject, UseThemeFunction> = {}
const responsive = (
styles: Exclude<ThemeUIStyleObject, ThemeDerivedStyles>
) => (theme?: Theme) => {
const next: Exclude<ThemeUIStyleObject, ThemeDerivedStyles> = {}
const breakpoints =
(theme && (theme.breakpoints as string[])) || defaultBreakpoints
const mediaQueries = [
null,
...breakpoints.map(n => `@media screen and (min-width: ${n})`),
...breakpoints.map((n) => `@media screen and (min-width: ${n})`),
]

for (const key in styles) {
const value =
typeof styles[key] === 'function' ? styles[key](theme) : styles[key]
for (const k in styles) {
const key = k as keyof typeof styles
let value = styles[key]
if (typeof value === 'function') {
value = value(theme || {})
}

if (value == null) continue
if (!Array.isArray(value)) {
Expand All @@ -243,7 +252,7 @@ const responsive = (styles: Exclude<ThemeUIStyleObject, UseThemeFunction>) => (
}
next[media] = next[media] || {}
if (value[i] == null) continue
next[media][key] = value[i]
;(next[media] as Record<string, any>)[key] = value[i]
}
}

Expand All @@ -259,22 +268,23 @@ export const css = (args: ThemeUIStyleObject = {}) => (
...defaultTheme,
...('theme' in props ? props.theme : props),
}
let result = {}
let result: CSSObject = {}
const obj = typeof args === 'function' ? args(theme) : args
const styles = responsive(obj)(theme)

for (const key in styles) {
const x = styles[key]
const x = styles[key as keyof typeof styles]
const val = typeof x === 'function' ? x(theme) : x

if (key === 'variant') {
const variant = css(get(theme, val))(theme)
const variant = css(get(theme, val as string))(theme)
result = { ...result, ...variant }
continue
}

if (val && typeof val === 'object') {
result[key] = css(val)(theme)
// TODO: val can also be an array here. Is this a bug? Can it be reproduced?
result[key] = css(val as ThemeUICSSObject)(theme)
continue
}

Expand All @@ -284,8 +294,8 @@ export const css = (args: ThemeUIStyleObject = {}) => (
const transform: any = get(transforms, prop, get)
const value = transform(scale, val, val)

if (multiples[prop]) {
const dirs = multiples[prop]
if (prop in multiples) {
const dirs = multiples[prop as keyof typeof multiples]

for (let i = 0; i < dirs.length; i++) {
result[dirs[i]] = value
Expand Down
Loading

0 comments on commit c4a59c7

Please sign in to comment.