diff --git a/.flowconfig b/.flowconfig index c2baf1d..f2822ef 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,7 +1,8 @@ [ignore] +/coverage/.* +/flow-coverage/.* +/lib/.* +.*/node_modules/findup/.* .*/node_modules/config-chain/.* .*/node_modules/npmconf/.* -.*/node_modules/findup/.* -.*/lib/.* -.*/coverage/.* -.*/flow-coverage/.* +.*/node_modules/flow-coverage-report/.* diff --git a/flow-typed/npm/jest_v19.x.x.js b/flow-typed/npm/jest_v19.x.x.js new file mode 100644 index 0000000..b12e3e9 --- /dev/null +++ b/flow-typed/npm/jest_v19.x.x.js @@ -0,0 +1,454 @@ +// flow-typed signature: b3ed97c44539e6cdbaf9032b315a2b31 +// flow-typed version: ea7ac31527/jest_v19.x.x/flow_>=v0.33.x + +type JestMockFn = { + (...args: Array): any, + /** + * An object for introspecting mock calls + */ + mock: { + /** + * An array that represents all calls that have been made into this mock + * function. Each call is represented by an array of arguments that were + * passed during the call. + */ + calls: Array>, + /** + * An array that contains all the object instances that have been + * instantiated from this mock function. + */ + instances: mixed, + }, + /** + * Resets all information stored in the mockFn.mock.calls and + * mockFn.mock.instances arrays. Often this is useful when you want to clean + * up a mock's usage data between two assertions. + */ + mockClear(): Function, + /** + * Resets all information stored in the mock. This is useful when you want to + * completely restore a mock back to its initial state. + */ + mockReset(): Function, + /** + * Accepts a function that should be used as the implementation of the mock. + * The mock itself will still record all calls that go into and instances + * that come from itself -- the only difference is that the implementation + * will also be executed when the mock is called. + */ + mockImplementation(fn: Function): JestMockFn, + /** + * Accepts a function that will be used as an implementation of the mock for + * one call to the mocked function. Can be chained so that multiple function + * calls produce different results. + */ + mockImplementationOnce(fn: Function): JestMockFn, + /** + * Just a simple sugar function for returning `this` + */ + mockReturnThis(): void, + /** + * Deprecated: use jest.fn(() => value) instead + */ + mockReturnValue(value: any): JestMockFn, + /** + * Sugar for only returning a value once inside your mock + */ + mockReturnValueOnce(value: any): JestMockFn, +} + +type JestAsymmetricEqualityType = { + /** + * A custom Jasmine equality tester + */ + asymmetricMatch(value: mixed): boolean, +} + +type JestCallsType = { + allArgs(): mixed, + all(): mixed, + any(): boolean, + count(): number, + first(): mixed, + mostRecent(): mixed, + reset(): void, +} + +type JestClockType = { + install(): void, + mockDate(date: Date): void, + tick(): void, + uninstall(): void, +} + +type JestMatcherResult = { + message?: string | ()=>string, + pass: boolean, +} + +type JestMatcher = (actual: any, expected: any) => JestMatcherResult; + +type JestExpectType = { + not: JestExpectType, + /** + * If you have a mock function, you can use .lastCalledWith to test what + * arguments it was last called with. + */ + lastCalledWith(...args: Array): void, + /** + * toBe just checks that a value is what you expect. It uses === to check + * strict equality. + */ + toBe(value: any): void, + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toBeCalled(): void, + /** + * Use .toBeCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toBeCalledWith(...args: Array): void, + /** + * Using exact equality with floating point numbers is a bad idea. Rounding + * means that intuitive things fail. + */ + toBeCloseTo(num: number, delta: any): void, + /** + * Use .toBeDefined to check that a variable is not undefined. + */ + toBeDefined(): void, + /** + * Use .toBeFalsy when you don't care what a value is, you just want to + * ensure a value is false in a boolean context. + */ + toBeFalsy(): void, + /** + * To compare floating point numbers, you can use toBeGreaterThan. + */ + toBeGreaterThan(number: number): void, + /** + * To compare floating point numbers, you can use toBeGreaterThanOrEqual. + */ + toBeGreaterThanOrEqual(number: number): void, + /** + * To compare floating point numbers, you can use toBeLessThan. + */ + toBeLessThan(number: number): void, + /** + * To compare floating point numbers, you can use toBeLessThanOrEqual. + */ + toBeLessThanOrEqual(number: number): void, + /** + * Use .toBeInstanceOf(Class) to check that an object is an instance of a + * class. + */ + toBeInstanceOf(cls: Class<*>): void, + /** + * .toBeNull() is the same as .toBe(null) but the error messages are a bit + * nicer. + */ + toBeNull(): void, + /** + * Use .toBeTruthy when you don't care what a value is, you just want to + * ensure a value is true in a boolean context. + */ + toBeTruthy(): void, + /** + * Use .toBeUndefined to check that a variable is undefined. + */ + toBeUndefined(): void, + /** + * Use .toContain when you want to check that an item is in a list. For + * testing the items in the list, this uses ===, a strict equality check. + */ + toContain(item: any): void, + /** + * Use .toContainEqual when you want to check that an item is in a list. For + * testing the items in the list, this matcher recursively checks the + * equality of all fields, rather than checking for object identity. + */ + toContainEqual(item: any): void, + /** + * Use .toEqual when you want to check that two objects have the same value. + * This matcher recursively checks the equality of all fields, rather than + * checking for object identity. + */ + toEqual(value: any): void, + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toHaveBeenCalled(): void, + /** + * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact + * number of times. + */ + toHaveBeenCalledTimes(number: number): void, + /** + * Use .toHaveBeenCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toHaveBeenCalledWith(...args: Array): void, + /** + * Check that an object has a .length property and it is set to a certain + * numeric value. + */ + toHaveLength(number: number): void, + /** + * + */ + toHaveProperty(propPath: string, value?: any): void, + /** + * Use .toMatch to check that a string matches a regular expression. + */ + toMatch(regexp: RegExp): void, + /** + * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. + */ + toMatchObject(object: Object): void, + /** + * This ensures that a React component matches the most recent snapshot. + */ + toMatchSnapshot(name?: string): void, + /** + * Use .toThrow to test that a function throws when it is called. + */ + toThrow(message?: string | Error): void, + /** + * Use .toThrowError to test that a function throws a specific error when it + * is called. The argument can be a string for the error message, a class for + * the error, or a regex that should match the error. + */ + toThrowError(message?: string | Error | RegExp): void, + /** + * Use .toThrowErrorMatchingSnapshot to test that a function throws a error + * matching the most recent snapshot when it is called. + */ + toThrowErrorMatchingSnapshot(): void, +} + +type JestObjectType = { + /** + * Disables automatic mocking in the module loader. + * + * After this method is called, all `require()`s will return the real + * versions of each module (rather than a mocked version). + */ + disableAutomock(): JestObjectType, + /** + * An un-hoisted version of disableAutomock + */ + autoMockOff(): JestObjectType, + /** + * Enables automatic mocking in the module loader. + */ + enableAutomock(): JestObjectType, + /** + * An un-hoisted version of enableAutomock + */ + autoMockOn(): JestObjectType, + /** + * Clears the mock.calls and mock.instances properties of all mocks. + * Equivalent to calling .mockClear() on every mocked function. + */ + clearAllMocks(): JestObjectType, + /** + * Resets the state of all mocks. Equivalent to calling .mockReset() on every + * mocked function. + */ + resetAllMocks(): JestObjectType, + /** + * Removes any pending timers from the timer system. + */ + clearAllTimers(): void, + /** + * The same as `mock` but not moved to the top of the expectation by + * babel-jest. + */ + doMock(moduleName: string, moduleFactory?: any): JestObjectType, + /** + * The same as `unmock` but not moved to the top of the expectation by + * babel-jest. + */ + dontMock(moduleName: string): JestObjectType, + /** + * Returns a new, unused mock function. Optionally takes a mock + * implementation. + */ + fn(implementation?: Function): JestMockFn, + /** + * Determines if the given function is a mocked function. + */ + isMockFunction(fn: Function): boolean, + /** + * Given the name of a module, use the automatic mocking system to generate a + * mocked version of the module for you. + */ + genMockFromModule(moduleName: string): any, + /** + * Mocks a module with an auto-mocked version when it is being required. + * + * The second argument can be used to specify an explicit module factory that + * is being run instead of using Jest's automocking feature. + * + * The third argument can be used to create virtual mocks -- mocks of modules + * that don't exist anywhere in the system. + */ + mock(moduleName: string, moduleFactory?: any): JestObjectType, + /** + * Resets the module registry - the cache of all required modules. This is + * useful to isolate modules where local state might conflict between tests. + */ + resetModules(): JestObjectType, + /** + * Exhausts the micro-task queue (usually interfaced in node via + * process.nextTick). + */ + runAllTicks(): void, + /** + * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), + * setInterval(), and setImmediate()). + */ + runAllTimers(): void, + /** + * Exhausts all tasks queued by setImmediate(). + */ + runAllImmediates(): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + */ + runTimersToTime(msToRun: number): void, + /** + * Executes only the macro-tasks that are currently pending (i.e., only the + * tasks that have been queued by setTimeout() or setInterval() up to this + * point) + */ + runOnlyPendingTimers(): void, + /** + * Explicitly supplies the mock object that the module system should return + * for the specified module. Note: It is recommended to use jest.mock() + * instead. + */ + setMock(moduleName: string, moduleExports: any): JestObjectType, + /** + * Indicates that the module system should never return a mocked version of + * the specified module from require() (e.g. that it should always return the + * real module). + */ + unmock(moduleName: string): JestObjectType, + /** + * Instructs Jest to use fake versions of the standard timer functions + * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, + * setImmediate and clearImmediate). + */ + useFakeTimers(): JestObjectType, + /** + * Instructs Jest to use the real versions of the standard timer functions. + */ + useRealTimers(): JestObjectType, + /** + * Creates a mock function similar to jest.fn but also tracks calls to + * object[methodName]. + */ + spyOn(object: Object, methodName: string): JestMockFn, +} + +type JestSpyType = { + calls: JestCallsType, +} + +/** Runs this function after every test inside this context */ +declare function afterEach(fn: Function): void; +/** Runs this function before every test inside this context */ +declare function beforeEach(fn: Function): void; +/** Runs this function after all tests have finished inside this context */ +declare function afterAll(fn: Function): void; +/** Runs this function before any tests have started inside this context */ +declare function beforeAll(fn: Function): void; +/** A context for grouping tests together */ +declare function describe(name: string, fn: Function): void; + +/** An individual test unit */ +declare var it: { + /** + * An individual test unit + * + * @param {string} Name of Test + * @param {Function} Test + */ + (name: string, fn?: Function): ?Promise, + /** + * Only run this test + * + * @param {string} Name of Test + * @param {Function} Test + */ + only(name: string, fn?: Function): ?Promise, + /** + * Skip running this test + * + * @param {string} Name of Test + * @param {Function} Test + */ + skip(name: string, fn?: Function): ?Promise, + /** + * Run the test concurrently + * + * @param {string} Name of Test + * @param {Function} Test + */ + concurrent(name: string, fn?: Function): ?Promise, +}; +declare function fit(name: string, fn: Function): ?Promise; +/** An individual test unit */ +declare var test: typeof it; +/** A disabled group of tests */ +declare var xdescribe: typeof describe; +/** A focused group of tests */ +declare var fdescribe: typeof describe; +/** A disabled individual test */ +declare var xit: typeof it; +/** A disabled individual test */ +declare var xtest: typeof it; + +/** The expect function is used every time you want to test a value */ +declare var expect: { + /** The object that you want to make assertions against */ + (value: any): JestExpectType, + /** Add additional Jasmine matchers to Jest's roster */ + extend(matchers: {[name:string]: JestMatcher}): void, + /** Add a module that formats application-specific data structures. */ + addSnapshotSerializer(serializer: (input: Object) => string): void, + assertions(expectedAssertions: number): void, + any(value: mixed): JestAsymmetricEqualityType, + anything(): void, + arrayContaining(value: Array): void, + objectContaining(value: Object): void, + /** Matches any received string that contains the exact expected string. */ + stringContaining(value: string): void, + stringMatching(value: string | RegExp): void, +}; + +// TODO handle return type +// http://jasmine.github.io/2.4/introduction.html#section-Spies +declare function spyOn(value: mixed, method: string): Object; + +/** Holds all functions related to manipulating test runner */ +declare var jest: JestObjectType + +/** + * The global Jamine object, this is generally not exposed as the public API, + * using features inside here could break in later versions of Jest. + */ +declare var jasmine: { + DEFAULT_TIMEOUT_INTERVAL: number, + any(value: mixed): JestAsymmetricEqualityType, + anything(): void, + arrayContaining(value: Array): void, + clock(): JestClockType, + createSpy(name: string): JestSpyType, + createSpyObj(baseName: string, methodNames: Array): {[methodName: string]: JestSpyType}, + objectContaining(value: Object): void, + stringMatching(value: string): void, +} diff --git a/package.json b/package.json index fcd63bf..1d6961f 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,15 @@ "lint": "eslint .", "flow": "flow check", "flow:coverage": "flow-coverage-report -i './src/**/*.js' -t html -t text", + "flow:copy": "flow-copy-source src lib", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", "codecov": "codecov", "commit": "git-cz", "prebuild": "rimraf ./lib", - "build": "babel --out-dir ./lib --ignore *.spec.js, ./src", + "compile": "babel --out-dir ./lib --ignore *.spec.js, ./src", + "build": "npm run compile && npm run flow:copy", "release": "semantic-release pre && npm publish && semantic-release post", "commitmsg": "validate-commit-msg", "precommit": "npm run lint && npm run test" @@ -34,6 +36,7 @@ "eslint": "^3.18.0", "eslint-config-ca": "^2.0.0", "flow-bin": "^0.42.0", + "flow-copy-source": "^1.1.0", "flow-coverage-report": "^0.3.0", "husky": "^0.13.3", "jest": "^19.0.2", diff --git a/src/Themer.js b/src/Themer.js index 572a926..8850c17 100644 --- a/src/Themer.js +++ b/src/Themer.js @@ -4,17 +4,35 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import Middleware from './middleware'; import Theme from './theme'; +type Options = { + middleware?: Array, +}; + export default class Themer { + /** + * Themer options + * @type {Options} + */ + options: Options; + + /** + * Themer middleware + * @type {Middleware} + */ + middleware: Middleware; + /** * Creates a new Themer instance * * @param {Options} options Themer options */ - constructor(options = {}) { + constructor(options?: Options = {}) { this.options = options; this.middleware = new Middleware(this.options.middleware); } @@ -34,7 +52,7 @@ export default class Themer { * @param {Function} func Function to invoke * @return {Themer} */ - setMiddleware(...middlewares) { + setMiddleware(...middlewares: Array) { this.middleware.set(...middlewares); return this; } @@ -42,12 +60,12 @@ export default class Themer { /** * Resolves all theme properties and middlewares * - * @param {Function} snippet Function that returns valid HTML markup + * @param {any} snippet to be decorated * @param {array} themes Array of themes * @param {object} globalVars global variables to use when resolving theme variables * @return {Object} Resolved theme and snippet attrs */ - resolveAttributes(snippet, themes, globalVars) { + resolveAttributes(snippet: any, themes: Array, globalVars?: Object) { const theme = new Theme(themes); const resolvedTheme = theme.resolve(globalVars); const resolvedSnippet = this.middleware.resolve(snippet, resolvedTheme); diff --git a/src/decorator/index.js b/src/decorator/index.js index 8e97d3b..0e8de84 100644 --- a/src/decorator/index.js +++ b/src/decorator/index.js @@ -5,19 +5,20 @@ */ /* eslint-disable import/prefer-default-export */ +// @flow import { themer } from '../'; import { mapThemeProps, applyVariantsProps } from '../utils'; -function variantsWrapper(snippet) { - return (props) => snippet(applyVariantsProps(props)); +function variantsWrapper(snippet: Function) { + return (props: Object) => snippet(applyVariantsProps(props)); } -export function createDecorator(customThemer) { +export function createDecorator(customThemer?: Object) { const themerInstance = customThemer || themer; - return rawTheme => inputSnippet => { + return (rawTheme: Object) => (inputSnippet: Function) => { const snippetWithVariants = variantsWrapper(inputSnippet); const { snippet, theme } = themerInstance.resolveAttributes(snippetWithVariants, [rawTheme]); - return (props) => snippet(mapThemeProps(props, theme)); + return (props: Object) => snippet(mapThemeProps(props, theme)); }; } diff --git a/src/index.js b/src/index.js index f7737fa..cc9eca9 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import { createThemer as create, mapThemeProps, @@ -11,6 +13,9 @@ import { } from './utils'; import { createDecorator } from './decorator'; +// export flow type for theme props +export type { ProvidedThemeProps } from './utils'; + const themer = create(); export { diff --git a/src/middleware/index.js b/src/middleware/index.js index f6e6672..b5688e0 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -4,16 +4,24 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import flowRight from 'lodash/flowRight'; export default class Middleware { + /** + * Registry of middlewares + * @type {Array} + */ + registry: Array; + /** * Creates a new Middleware instance * * @param {Array} middleware middleware functions to run */ - constructor(middleware = []) { + constructor(middleware?: Array = []) { this.registry = []; middleware.forEach(func => this.set(func)); @@ -33,7 +41,7 @@ export default class Middleware { * * @param {Function[]} func */ - set(...middlewares) { + set(...middlewares: Array) { middlewares.forEach(func => this.registry.push(func)); } @@ -45,7 +53,7 @@ export default class Middleware { * @param {Object} theme Executes all middleware function on snippet * @return {Function} Resolved results of all middleware methods */ - resolve(snippet, theme) { + resolve(snippet: any, theme: Object) { return flowRight(this.registry)(snippet, theme); } diff --git a/src/theme/index.js b/src/theme/index.js index 8986230..c220559 100644 --- a/src/theme/index.js +++ b/src/theme/index.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import uuid from 'uuid'; import isFunction from 'lodash/isFunction'; import isPlainObject from 'lodash/isPlainObject'; @@ -14,10 +16,16 @@ import { export default class Theme { + /** + * Combined theme object + * @type {Object} + */ + theme: Object; + /** * Creates a new Theme instance */ - constructor(themes) { + constructor(themes?: Array) { this.theme = {}; this.setup(themes); @@ -26,10 +34,10 @@ export default class Theme { /** * Run setup actions * - * @param {Array} themes Themes collection passed as constructor prop + * @param {Array} themes Themes collection passed as constructor prop * @return {Theme} */ - setup(themes) { + setup(themes?: Array) { if (themes && themes.length) { this.theme = Theme.combine(themes); } @@ -52,9 +60,17 @@ export default class Theme { * @param {*} theme The theme object * @throws {Error} If the theme object does not exist or is of wrong type */ - static validate(theme) { - if (!isPlainObject(theme) && !isFunction(theme)) { - throw new Error('Theme must either an object or function'); + static validate(theme: Object) { + if (!theme || !isPlainObject(theme)) { + throw new Error('Theme must be an object'); + } + + if (theme.styles && !isPlainObject(theme.styles) && !isFunction(theme.styles)) { + throw new Error('Theme styles must be either an object or a function'); + } + + if (theme.variables && !isPlainObject(theme.variables) && !isFunction(theme.variables)) { + throw new Error('Theme variables must be either an object or a function'); } return true; @@ -70,7 +86,7 @@ export default class Theme { * @throws {Error} If the theme object has not been passed in * @public */ - static parse(theme) { + static parse(theme: Object) { Theme.validate(theme); if (theme.styles || theme.variables) { @@ -89,14 +105,14 @@ export default class Theme { * @param {Array} themes Array of theme objects * @return {Array} Array of combined and formatted theme objects */ - static combine(themes) { + static combine(themes: Array): Object { if (!themes || !themes.length) { return {}; } const truthy = val => val; - const resolvedTheme = themes + const resolvedTheme: Object = themes .map(Theme.parse) .filter(truthy) .reduce((previousTheme, currentTheme) => ({ @@ -119,7 +135,7 @@ export default class Theme { return this.theme.id; } - resolve(globalVars) { + resolve(globalVars?: Object) { const variables = resolveValue(this.theme.variables, globalVars); const styles = resolveValue(this.theme.styles, variables); const variants = resolveValue(this.theme.variants); diff --git a/src/utils/index.js b/src/utils/index.js index 3fcd89c..b4bddbd 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import isFunction from 'lodash/isFunction'; import find from 'lodash/find'; import forEach from 'lodash/forEach'; @@ -18,7 +20,7 @@ import Themer from './../Themer'; * @param {Object} options Themer options * @return {Themer} */ -export function createThemer(options = {}) { +export function createThemer(options?: Object = {}) { return new Themer(options); } @@ -29,8 +31,8 @@ export function createThemer(options = {}) { * @return {Boolean} If any values are of type function * @public */ -export function arrayHasFunction(arr = []) { - const hasFunction = find(arr, (val) => isFunction(val)); +export function arrayHasFunction(arr: Array) { + const hasFunction = find(arr, (val: any) => isFunction(val)); return !!hasFunction; } @@ -43,7 +45,7 @@ export function arrayHasFunction(arr = []) { * @return {Object} Contains only flat properties, no functions * @public */ -export function resolveArray(arrayToResolve, ...args) { +export function resolveArray(arrayToResolve: any, ...args: Array) { const arr = Array.isArray(arrayToResolve) ? arrayToResolve : [arrayToResolve]; return arr.reduce((accumulator, val) => { @@ -67,7 +69,7 @@ export function resolveArray(arrayToResolve, ...args) { * @param {Object} globalVariables Variables defined by the gloval application theme * @return {Object} Resolved theme variables, where local vars take priority */ -export function resolveValue(attr, ...args) { +export function resolveValue(attr: any, ...args: Array) { if (isFunction(attr) || arrayHasFunction(attr)) { return resolveArray(attr, ...args); } @@ -86,7 +88,7 @@ export function resolveValue(attr, ...args) { * @return {Array} Array containing both attribute value from both themes * @public */ -export function combineByAttributes(attr, obj1 = {}, obj2 = {}) { +export function combineByAttributes(attr: string, obj1: Object = {}, obj2: Object = {}) { if (!obj1[attr] && !obj2[attr]) { return {}; } @@ -110,6 +112,11 @@ export function combineByAttributes(attr, obj1 = {}, obj2 = {}) { return [obj1[attr], obj2[attr]]; } +/** + * Type definition for theme-related props + * @type {ProvidedThemeProps} + */ +export type ProvidedThemeProps = { theme: Object, classes: Object }; /** * Map resolved theme to snippet props @@ -119,7 +126,10 @@ export function combineByAttributes(attr, obj1 = {}, obj2 = {}) { * @return {Object} The mapped themed props * @public */ -export function mapThemeProps(props, resolvedTheme) { +export function mapThemeProps( + props: OriginalProps, + resolvedTheme: Object, +): OriginalProps & ProvidedThemeProps { return { ...props, theme: resolvedTheme, classes: resolvedTheme.styles }; } @@ -131,25 +141,25 @@ export function mapThemeProps(props, resolvedTheme) { * @return {boolean} true if values match * @public */ -function verifyVariantPropValue(variantPropVal, propVal) { +function verifyVariantPropValue(variantPropVal: any, propVal: any) { return (variantPropVal === propVal) || (!variantPropVal && !propVal); } /** * Append variants passed as "true" props to the root style element * - * @param {Object} props The props passed to the render method - * @return {Object} The styles as per post-variant processing + * @param {T:Object} props The props passed to the render method + * @return {T} The styles as per post-variant processing * @public */ -export function applyVariantsProps(props) { +export function applyVariantsProps(props: T): T { const resolvedTheme = props.theme; if (!resolvedTheme || !resolvedTheme.variants || !resolvedTheme.styles) { return props; } const mappedStyles = extend({ root: '' }, resolvedTheme.styles); - const mappedProps = extend({}, props); + const mappedProps: T = extend({}, props); const renderedVariants = {}; forEach(resolvedTheme.variants, (variantValue, variantKey) => { diff --git a/tests/Themer.spec.js b/tests/Themer.spec.js index 455ead0..d966db2 100644 --- a/tests/Themer.spec.js +++ b/tests/Themer.spec.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import Themer from '../src/Themer'; import { testThemeSimple, @@ -16,7 +18,7 @@ describe('Themer', () => { const spy2 = jest.fn().mockImplementation((component) => component); const testInstance = new Themer(); testInstance.setMiddleware(spy1, spy2); - testInstance.resolveAttributes(snippet, testThemeSimple); + testInstance.resolveAttributes(snippet, [testThemeSimple]); expect(spy1).toHaveBeenCalled(); expect(spy2).toHaveBeenCalled(); diff --git a/tests/decorator/index.spec.js b/tests/decorator/index.spec.js index d753765..1dd252f 100644 --- a/tests/decorator/index.spec.js +++ b/tests/decorator/index.spec.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import { create, mapThemeProps } from '../../src'; import { createDecorator } from '../../src/decorator'; import { diff --git a/tests/fixtures/index.js b/tests/fixtures/index.js index c26f337..875c30c 100644 --- a/tests/fixtures/index.js +++ b/tests/fixtures/index.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + export const testThemeSimple = { styles: { root: 'big-text-class', @@ -32,12 +34,17 @@ export const testThemeFunction = { variables: { color: 'blue', }, - styles: (_, vars) => ({ + styles: (_: any, vars?: Object = {}) => ({ root: { color: vars.color }, }), }; -export const snippet = ({ classes, content }) => +type Props = { + classes?: Object, + content?: string, +}; + +export const snippet = ({ classes = {}, content = '' }: Props) => `

` + `${content}` + '

'; diff --git a/tests/index.spec.js b/tests/index.spec.js index 3d032c6..289ec70 100644 --- a/tests/index.spec.js +++ b/tests/index.spec.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import themerDecorator, { create, themer as themerInstance, diff --git a/tests/middleware/index.spec.js b/tests/middleware/index.spec.js index aad0e4a..5a1190c 100644 --- a/tests/middleware/index.spec.js +++ b/tests/middleware/index.spec.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import { create } from '../../src'; describe('middleware', () => { diff --git a/tests/theme/index.spec.js b/tests/theme/index.spec.js index 7f4c9fe..9c2d877 100644 --- a/tests/theme/index.spec.js +++ b/tests/theme/index.spec.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import Theme from '../../src/theme'; import { testThemeSimple, testThemeVariants } from '../fixtures'; @@ -22,6 +24,13 @@ describe('theme', () => { expect(theme.get().styles).toEqual(testThemeSimple.styles); }); + it('should allow to set themes via `setup` method', () => { + const theme = new Theme(); + theme.setup([testThemeSimple]); + + expect(theme.get().styles).toEqual(testThemeSimple.styles); + }); + it('should provide a getter for the theme id', () => { const theme = new Theme([testThemeSimple]); @@ -50,10 +59,19 @@ describe('theme', () => { expect(resolvedTheme.styles).toEqual(testThemeSimple.styles); }); - it('should throw an error if theme is not an object or function', () => { + it('should throw an error if theme is not an object', () => { + // $FlowFixMe expect(() => new Theme([1])).toThrow(); }); + it('should throw an error theme.styles is not an object or a function', () => { + expect(() => new Theme([{ styles: 1 }])).toThrow(); + }); + + it('should throw an error theme.variables is not an object or a function', () => { + expect(() => new Theme([{ variables: 1 }])).toThrow(); + }); + it('should return an object of variants if supplied', () => { const theme = new Theme([testThemeVariants]); const resolvedTheme = theme.resolve(); @@ -93,6 +111,7 @@ describe('theme', () => { describe('combine', () => { it('should return an empty object if no argument is passed', () => { + // $FlowFixMe const combinedTheme = Theme.combine(); expect(combinedTheme).toEqual({}); }); diff --git a/tests/utils/index.spec.js b/tests/utils/index.spec.js index 345702d..7da1203 100644 --- a/tests/utils/index.spec.js +++ b/tests/utils/index.spec.js @@ -4,6 +4,8 @@ * of the MIT license. See the LICENSE file for details. */ +// @flow + import { createThemer, arrayHasFunction, @@ -67,6 +69,7 @@ describe('utils', () => { describe('combineByAttributes', () => { it('should return an empty object if no argument is passed', () => { + // $FlowFixMe const combinedObj = combineByAttributes(); expect(combinedObj).toEqual({}); });