From 43c6a4fee09bfdf3f7f39e520ef420f3e128bb8b Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Fri, 28 Jul 2017 12:58:06 +0200 Subject: [PATCH 01/27] feat(defaultProfiles): Providing default profiles --- docs/API.md | 3 +-- src/constants/defaults.js | 25 ++++++++++++++++++++++++- src/index.js | 4 ++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index a453107..5957799 100644 --- a/docs/API.md +++ b/docs/API.md @@ -53,8 +53,7 @@ The concept is very similar to `Provider` component from `react-redux`. type Options = { region?: { top: number, left: number, bottom: number, right: number }, maximumColorCount?: number = 16, - type?: ColorProfile = 'vibrant', - types?: Array = [] + type?: ColorProfile | Array = 'vibrant', } ``` diff --git a/src/constants/defaults.js b/src/constants/defaults.js index f4a0ed7..78516e6 100644 --- a/src/constants/defaults.js +++ b/src/constants/defaults.js @@ -2,6 +2,13 @@ import type { Region, Options, Swatch } from '../types'; +const defaultVibrant = '#757575'; +const defaultLightVibrant = '#E0E0E0'; +const defaultDarkVibrant = '#212121'; +const defaultMuted = '#9E9E9E'; +const defaultLightMuted = '#BDBDBD'; +const defaultDarkMuted = '#616161'; + export const defaultRegion: Region = { top: 0, right: 0, @@ -15,9 +22,25 @@ export const defaultOptions: Options = { type: 'vibrant', }; -export const nullSwatch: Swatch = { +export const defaultLightSwatch: Swatch = { population: 0, color: '#000000', bodyTextColor: '#000000', titleTextColor: '#000000', }; + +const defaultDarkSwatch: Swatch = { + population: 0, + color: '#000000', + bodyTextColor: '#FFFFFF', + titleTextColor: '#FFFFFF', +}; + +export const defaultSwatch = { + vibrant: { ...defaultDarkSwatch, color: defaultVibrant }, + lightVibrant: { ...defaultLightSwatch, color: defaultLightVibrant }, + darkVibrant: { ...defaultLightSwatch, color: defaultDarkVibrant }, + muted: { ...defaultDarkSwatch, color: defaultMuted }, + lightMuted: { ...defaultLightSwatch, color: defaultLightMuted }, + darkMuted: { ...defaultLightSwatch, color: defaultDarkMuted }, +}; diff --git a/src/index.js b/src/index.js index 781ec2f..6e4e1dd 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ import { NativeModules } from 'react-native'; import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import isEqual from 'lodash/isEqual'; -import { defaultOptions, nullSwatch } from './constants/defaults'; +import { defaultOptions, defaultLightSwatch } from './constants/defaults'; import validate from './utils/validate'; import type { Image, @@ -56,7 +56,7 @@ const namespace: Namespace = { }, ); Object.keys(paletteInstance).forEach((profile: ColorProfile) => { - if (isEqual(paletteInstance[profile], nullSwatch)) { + if (isEqual(paletteInstance[profile], defaultLightSwatch)) { paletteInstance[profile] = null; } }); From d81f1a8a55d266c645a86a5fb6913bd7c7d45ce9 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Fri, 28 Jul 2017 14:28:10 +0200 Subject: [PATCH 02/27] feat(defaultProfiles): Merging defaults in PaletteProvider --- docs/API.md | 2 +- src/PaletteProvider.js | 29 ++++++++++++++++++++++++++++- src/constants/defaults.js | 2 +- src/withPalette.js | 4 ++-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index 5957799..34fbb9e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -87,7 +87,7 @@ The concept is very similar to `Provider` component from `react-redux`. * `onInit?: () => void` - (optional) - Init handler, called when the `MaterialPaletteProvider` is just about to start creating the palette. -* `onFinish?: (palette: PaletteInstance, globalDefaults: PaletteDefaults) => void` - (optional) - Finish handler, called when the palette is created, but before it gets propagated to _connected_ components - use it, if you want to mutate the palette instance. +* `onFinish?: (palette: PaletteInstance) => void` - (optional) - Finish handler, called when the palette is created, but before it gets propagated to _connected_ components - use it, if you want to mutate the palette instance. If some profiles are not available for the provided image, the defaults will apply, taking precedence the ones you passed to the component as `this.props.defaults`. * `children: React$Element<*>`, - (__required__) - Children elements - the rest of your app's component tree. diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 5a16e5c..7092550 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -4,6 +4,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import createEventEmitter from './createEventEmitter'; import MaterialPalette from './index'; +import { defaultSwatches } from './constants/defaults'; import type { PaletteInstance, Image, Options, PaletteDefaults } from './types'; @@ -93,11 +94,37 @@ export default class MaterialPaletteProvider }; } + _mergeWithDefaults(palette: PaletteInstance) { + const defaults = { + ...defaultSwatches, + ...Object.keys(this.props.defaults).reduce( + (acc: *, profile: string) => ({ + ...acc, + [profile]: { ...this.props.defaults[profile], population: 0 }, + }), + {}, + ), + }; + return { + ...Object.keys(palette) + .filter((profile: string) => !!palette[profile]) // Stripping out unavailable profiles + .reduce( + (acc: *, profile: string) => ({ + ...acc, + [profile]: palette[profile], + }), + {}, + ), + ...defaults, + }; + } + componentWillMount() { execIfFunction(this.props.onInit); MaterialPalette.create(this.props.image, this.props.options) .then((palette: PaletteInstance) => { - execIfFunction(this.props.onFinish, palette, this.props.defaults); + const paletteWithDefaults = this._mergeWithDefaults(palette); + execIfFunction(this.props.onFinish, paletteWithDefaults); if (!this.props.forceRender) { this.setState({ palette }); } diff --git a/src/constants/defaults.js b/src/constants/defaults.js index 78516e6..232e9f7 100644 --- a/src/constants/defaults.js +++ b/src/constants/defaults.js @@ -36,7 +36,7 @@ const defaultDarkSwatch: Swatch = { titleTextColor: '#FFFFFF', }; -export const defaultSwatch = { +export const defaultSwatches = { vibrant: { ...defaultDarkSwatch, color: defaultVibrant }, lightVibrant: { ...defaultLightSwatch, color: defaultLightVibrant }, darkVibrant: { ...defaultLightSwatch, color: defaultDarkVibrant }, diff --git a/src/withPalette.js b/src/withPalette.js index a5308cf..0605a88 100644 --- a/src/withPalette.js +++ b/src/withPalette.js @@ -14,10 +14,10 @@ type State = { /** * Connect component to a material palette notification channel in order to obtain - * pallete instance when palette is resolved. + * palette instance when palette is resolved. * Prop `palette` will be passed to wrapped component with either `null` or with palette instance. * - * If `mapPaletteToStyle` is specified, it will be evaluated when pallete is available and + * If `mapPaletteToStyle` is specified, it will be evaluated when palette is available and * the results will be passed to a `style` prop to wrapped component. */ export default function withMaterialPalette( From 0f269957e30626f0ed27781f020b79eae62c5c44 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Fri, 28 Jul 2017 17:13:02 +0200 Subject: [PATCH 03/27] Addressing flow issues --- src/PaletteProvider.js | 27 ++++++++++++++++++++++----- yarn.lock | 12 ++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 7092550..f5dadbc 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -6,7 +6,13 @@ import createEventEmitter from './createEventEmitter'; import MaterialPalette from './index'; import { defaultSwatches } from './constants/defaults'; -import type { PaletteInstance, Image, Options, PaletteDefaults } from './types'; +import type { + PaletteInstance, + Image, + Options, + PaletteDefaults, + ColorProfile, +} from './types'; export const KEY = '__react-native-material-palette__'; @@ -97,19 +103,19 @@ export default class MaterialPaletteProvider _mergeWithDefaults(palette: PaletteInstance) { const defaults = { ...defaultSwatches, - ...Object.keys(this.props.defaults).reduce( + ...Object.keys(this.props.defaults || {}).reduce( (acc: *, profile: string) => ({ ...acc, - [profile]: { ...this.props.defaults[profile], population: 0 }, + [profile]: { ...(this.props.defaults[profile] || {}), population: 0 }, }), {}, ), }; return { ...Object.keys(palette) - .filter((profile: string) => !!palette[profile]) // Stripping out unavailable profiles + .filter((profile: ColorProfile) => !!palette[profile]) // Stripping out unavailable profiles .reduce( - (acc: *, profile: string) => ({ + (acc: *, profile: ColorProfile) => ({ ...acc, [profile]: palette[profile], }), @@ -119,7 +125,18 @@ export default class MaterialPaletteProvider }; } + _validateDefaults() { + if (this.props.defaults) { + if (typeof this.props.defaults !== 'object') { + throw new Error('this.props.defaults should be an object'); + } else { + // TODO validate defaults + } + } + } + componentWillMount() { + this._validateDefaults(); execIfFunction(this.props.onInit); MaterialPalette.create(this.props.image, this.props.options) .then((palette: PaletteInstance) => { diff --git a/yarn.lock b/yarn.lock index 71fe108..15fd374 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3106,14 +3106,14 @@ js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" -js-yaml@3.6.1: +js-yaml@3.6.1, js-yaml@^3.5.1: version "3.6.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" dependencies: argparse "^1.0.7" esprima "^2.6.0" -js-yaml@^3.5.1, js-yaml@^3.7.0: +js-yaml@^3.7.0: version "3.8.4" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6" dependencies: @@ -3505,13 +3505,13 @@ mime-db@~1.23.0: version "1.23.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.23.0.tgz#a31b4070adaea27d732ea333740a64d0ec9a6659" -mime-types@2.1.11: +mime-types@2.1.11, mime-types@~2.1.7: version "2.1.11" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.11.tgz#c259c471bda808a85d6cd193b430a5fae4473b3c" dependencies: mime-db "~1.23.0" -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.6, mime-types@~2.1.7, mime-types@~2.1.9: +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.6, mime-types@~2.1.9: version "2.1.15" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" dependencies: @@ -4410,7 +4410,7 @@ replace-ext@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" -request@2.79.0: +request@2.79.0, request@^2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" dependencies: @@ -4435,7 +4435,7 @@ request@2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" -request@^2.79.0, request@^2.81.0: +request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: From 0f490675e73ca9d2b388c51999b8cfd80b8dcb64 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 11:20:20 +0200 Subject: [PATCH 04/27] Passing palette with defaults to connected components and event emitter --- src/PaletteProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index f5dadbc..5f230a2 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -143,10 +143,10 @@ export default class MaterialPaletteProvider const paletteWithDefaults = this._mergeWithDefaults(palette); execIfFunction(this.props.onFinish, paletteWithDefaults); if (!this.props.forceRender) { - this.setState({ palette }); + this.setState({ paletteWithDefaults }); } this.eventEmitter.publish({ - palette, + paletteWithDefaults, globalDefaults: this.props.defaults, }); }) From 665bfeb5ae9da1c4c77623d706d6088217f2d8a5 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 11:34:20 +0200 Subject: [PATCH 05/27] Attempting to solve flow errors --- src/PaletteProvider.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 5f230a2..09677c6 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -6,13 +6,7 @@ import createEventEmitter from './createEventEmitter'; import MaterialPalette from './index'; import { defaultSwatches } from './constants/defaults'; -import type { - PaletteInstance, - Image, - Options, - PaletteDefaults, - ColorProfile, -} from './types'; +import type { PaletteInstance, Image, Options, PaletteDefaults } from './types'; export const KEY = '__react-native-material-palette__'; @@ -103,8 +97,11 @@ export default class MaterialPaletteProvider _mergeWithDefaults(palette: PaletteInstance) { const defaults = { ...defaultSwatches, - ...Object.keys(this.props.defaults || {}).reduce( - (acc: *, profile: string) => ({ + ...Object.keys( + this.props.defaults || {}, + ).reduce( + // eslint-disable-next-line flowtype/require-parameter-type + (acc, profile) => ({ ...acc, [profile]: { ...(this.props.defaults[profile] || {}), population: 0 }, }), @@ -113,9 +110,11 @@ export default class MaterialPaletteProvider }; return { ...Object.keys(palette) - .filter((profile: ColorProfile) => !!palette[profile]) // Stripping out unavailable profiles + // eslint-disable-next-line flowtype/require-parameter-type + .filter(profile => !!palette[profile]) // Stripping out unavailable profiles .reduce( - (acc: *, profile: ColorProfile) => ({ + // eslint-disable-next-line flowtype/require-parameter-type + (acc, profile) => ({ ...acc, [profile]: palette[profile], }), From 2d564b44db85b567aac4be242adf4853bb5f8832 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 11:47:51 +0200 Subject: [PATCH 06/27] Small fix --- src/PaletteProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 09677c6..ef434d1 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -142,10 +142,10 @@ export default class MaterialPaletteProvider const paletteWithDefaults = this._mergeWithDefaults(palette); execIfFunction(this.props.onFinish, paletteWithDefaults); if (!this.props.forceRender) { - this.setState({ paletteWithDefaults }); + this.setState({ palette: paletteWithDefaults }); } this.eventEmitter.publish({ - paletteWithDefaults, + palette: paletteWithDefaults, globalDefaults: this.props.defaults, }); }) From d263d3541a4ef90b394dff2fdc0f6e86c9a5aeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 1 Aug 2017 11:59:41 +0200 Subject: [PATCH 07/27] fix flow error --- src/PaletteProvider.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index ef434d1..ef00ad6 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -6,7 +6,13 @@ import createEventEmitter from './createEventEmitter'; import MaterialPalette from './index'; import { defaultSwatches } from './constants/defaults'; -import type { PaletteInstance, Image, Options, PaletteDefaults } from './types'; +import type { + PaletteInstance, + Image, + Options, + PaletteDefaults, + ColorProfile, +} from './types'; export const KEY = '__react-native-material-palette__'; @@ -97,24 +103,26 @@ export default class MaterialPaletteProvider _mergeWithDefaults(palette: PaletteInstance) { const defaults = { ...defaultSwatches, - ...Object.keys( + ...((Object.keys( this.props.defaults || {}, - ).reduce( - // eslint-disable-next-line flowtype/require-parameter-type - (acc, profile) => ({ + ): any): ColorProfile[]).reduce( + (acc: *, profile: ColorProfile) => ({ ...acc, - [profile]: { ...(this.props.defaults[profile] || {}), population: 0 }, + [profile]: { + ...((this.props.defaults + ? this.props.defaults[profile] + : defaultSwatches[profile]) || defaultSwatches[profile]), + population: 0, + }, }), {}, ), }; return { - ...Object.keys(palette) - // eslint-disable-next-line flowtype/require-parameter-type - .filter(profile => !!palette[profile]) // Stripping out unavailable profiles + ...((Object.keys(palette): any): ColorProfile[]) + .filter((profile: ColorProfile) => !!palette[profile]) // Stripping out unavailable profiles .reduce( - // eslint-disable-next-line flowtype/require-parameter-type - (acc, profile) => ({ + (acc: *, profile: ColorProfile) => ({ ...acc, [profile]: palette[profile], }), From 0d2a8d0c3ac98ec18caaaaf661863f28fea46bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 1 Aug 2017 12:00:37 +0200 Subject: [PATCH 08/27] fix and remove flow ignore comment --- src/withPalette.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/withPalette.js b/src/withPalette.js index 0605a88..f14021e 100644 --- a/src/withPalette.js +++ b/src/withPalette.js @@ -79,8 +79,7 @@ export default function withMaterialPalette( ...Object.keys(localDefaults || {}), ].reduce( (acc: *, key: string) => { - // $FlowFixMe - const profile = (key: ColorProfile); + const profile = ((key: any): ColorProfile); return { ...acc, [key]: { From d971eef642bf7a020601a87f3cc00e3f154285d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 1 Aug 2017 12:04:26 +0200 Subject: [PATCH 09/27] remove globalDefaults from withPalette --- src/PaletteProvider.js | 1 - src/withPalette.js | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index ef00ad6..b2be309 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -154,7 +154,6 @@ export default class MaterialPaletteProvider } this.eventEmitter.publish({ palette: paletteWithDefaults, - globalDefaults: this.props.defaults, }); }) .catch((error: Error) => { diff --git a/src/withPalette.js b/src/withPalette.js index f14021e..6a4e5c1 100644 --- a/src/withPalette.js +++ b/src/withPalette.js @@ -56,7 +56,6 @@ export default function withMaterialPalette( this.unsubscribe = subscribe((data: { palette: PaletteInstance, - globalDefaults: PaletteDefaults, }) => { if (data) { this.setState(data); @@ -71,11 +70,10 @@ export default function withMaterialPalette( } _mergePaletteWithDefaults(): PaletteInstance { - const { palette, globalDefaults } = this.state; + const { palette } = this.state; return [ ...Object.keys(palette || {}), - ...Object.keys(globalDefaults || {}), ...Object.keys(localDefaults || {}), ].reduce( (acc: *, key: string) => { @@ -85,9 +83,6 @@ export default function withMaterialPalette( [key]: { population: 0, ...acc[key], - ...(globalDefaults && globalDefaults[profile] - ? globalDefaults[profile] - : {}), ...(localDefaults && localDefaults[profile] ? localDefaults[profile] : {}), From bb7cd130eb20c43b998fb90b628110eddf20d167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Trys=C5=82a?= Date: Tue, 1 Aug 2017 12:11:03 +0200 Subject: [PATCH 10/27] replace default export with named one for create function --- README.md | 8 ++-- docs/API.md | 24 ----------- example/index.android.js | 4 +- src/PaletteProvider.js | 4 +- src/__tests__/index.test.js | 4 +- src/index.js | 83 +++++++++++++------------------------ 6 files changed, 39 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 3159303..93bd658 100644 --- a/README.md +++ b/README.md @@ -80,16 +80,16 @@ create: (image: Image, options?: Options) => Promise ##### Creating a palette from a network resource, with 'vibrant' color profile, maximumColorCount = 16 and the whole region of the image (default behaviour) ```js -import MaterialPalette from "react-native-material-palette"; +import { createMaterialPalette } from "react-native-material-palette"; -const palette = await MaterialPalette.create({ uri: 'http://dummySite/images/yummy.jpg' }); +const palette = await createMaterialPalette({ uri: 'http://dummySite/images/yummy.jpg' }); ``` ##### Creating a palette from an internal image asset, with 'muted' and 'lightVibrant' color profiles, maximumColorCount = 32 and a specific region of the image ```js -import MaterialPalette from "react-native-material-palette"; +import { createMaterialPalette } from "react-native-material-palette"; -const palette = await MaterialPalette.create(require('./assets/image.jpg'), { +const palette = await createMaterialPalette(require('./assets/image.jpg'), { region: { top: 0, left: 0, bottom: 50, right: 50}, maximumColorCount: 32, type: ['muted', 'lightVibrant'], diff --git a/docs/API.md b/docs/API.md index 34fbb9e..d8baddc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -2,8 +2,6 @@ ## `MaterialPaletteProvider` -__Also available from the default import:__ `MaterialPalette.PaletteProvider` - ### Example of usage: ```javascript import React from 'react'; @@ -30,16 +28,6 @@ class App extends React.Component { } ``` -> You can import the component directly, as a named import: -> ```javascript -> import { MaterialPaletteProvider } from 'react-native-material-palette'; -> ``` -> or using the default import and accessing the component using `PaletteProvider` property: -> ```javascript -> import MaterialPalette from 'react-native-material-palette'; -> // MaterialPalette.PaletteProvider -> ``` - ### Description `MaterialPaletteProvider` is a component, which handles palette creation and provides the access to the palette instance for _connected_ components (via `withMaterialPalette`) using context. Ideally, `MaterialPaletteProvider` should be placed at the top of components tree, so that all nested components can _connect_ to it. By default it will render `null` when the palette is being created unless either `forceRender` or `LoaderComponent` is specified. @@ -95,8 +83,6 @@ The concept is very similar to `Provider` component from `react-redux`. ## `withMaterialPalette` -__Also available from the default import:__ `MaterialPalette.withPalette` - ### Example of usage: ```javascript import React from 'react'; @@ -111,16 +97,6 @@ export default withMaterialPalette( )(Text); ``` -> You can import the function directly, as a named import: -> ```javascript -> import { withMaterialPalette } from 'react-native-material-palette'; -> ``` -> or using the default import and accessing the component using `withPalette` property: -> ```javascript -> import MaterialPalette from 'react-native-material-palette'; -> // MaterialPalette.withPalette -> ``` - ### Description `withMaterialPalette` is a function that returns a Higher Order Component (HOC), which allows to seemlessy _connect_ to the `MaterialPaletteProvider` and get the palette instance via context. diff --git a/example/index.android.js b/example/index.android.js index 06e5720..0864f8d 100644 --- a/example/index.android.js +++ b/example/index.android.js @@ -7,7 +7,7 @@ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, Image } from 'react-native'; -import MaterialPalette from 'react-native-material-palette'; +import { createMaterialPalette } from 'react-native-material-palette'; export default class TestPalette extends Component { state = { @@ -16,7 +16,7 @@ export default class TestPalette extends Component { }; async componentDidMount() { - const palette = await MaterialPalette.create( + const palette = await createMaterialPalette( require('./assets/wroclaw.jpg'), // eslint-disable-line global-require { type: ['lightMuted', 'darkVibrant', 'vibrant'], diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index b2be309..a3d5f4a 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import createEventEmitter from './createEventEmitter'; -import MaterialPalette from './index'; +import { createMaterialPalette } from './index'; import { defaultSwatches } from './constants/defaults'; import type { @@ -145,7 +145,7 @@ export default class MaterialPaletteProvider componentWillMount() { this._validateDefaults(); execIfFunction(this.props.onInit); - MaterialPalette.create(this.props.image, this.props.options) + createMaterialPalette(this.props.image, this.props.options) .then((palette: PaletteInstance) => { const paletteWithDefaults = this._mergeWithDefaults(palette); execIfFunction(this.props.onFinish, paletteWithDefaults); diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js index 82866e3..019f516 100644 --- a/src/__tests__/index.test.js +++ b/src/__tests__/index.test.js @@ -1,9 +1,9 @@ /* eslint-env jest */ -import MaterialPalette from '../'; +import { createMaterialPalette } from '../'; describe('reactNativeMaterialPalette', () => { it('should return argument', () => { - expect(typeof MaterialPalette.create).toBe('function'); + expect(typeof createMaterialPalette).toBe('function'); }); }); diff --git a/src/index.js b/src/index.js index 6e4e1dd..15949fe 100644 --- a/src/index.js +++ b/src/index.js @@ -6,64 +6,39 @@ import resolveAssetSource import isEqual from 'lodash/isEqual'; import { defaultOptions, defaultLightSwatch } from './constants/defaults'; import validate from './utils/validate'; -import type { - Image, - PaletteInstance, - Options, - ColorProfile, - PaletteDefaults, -} from './types'; +import type { Image, PaletteInstance, Options, ColorProfile } from './types'; import PaletteProvider from './PaletteProvider'; import withPalette from './withPalette'; -export const MaterialPaletteProvider = PaletteProvider; -export const withMaterialPalette = withPalette; - -/** API */ - -type Namespace = { - create: (image: Image, options?: Options) => Promise, - PaletteProvider: Class>, - withPalette: ( - mapPaletteToStyle?: (palette: PaletteInstance) => { - [key: string]: mixed, - }, - localDefaults?: PaletteDefaults, - ) => (WrappedComponent: ReactClass<*>) => Class>, -}; - -const namespace: Namespace = { - async create( - image: Image, - options?: Options = defaultOptions, - ): Promise { - validate(image, options); - const { +export async function createMaterialPalette( + image: Image, + options?: Options = defaultOptions, +): Promise { + validate(image, options); + const { + region, + maximumColorCount, + type, + } = { ...defaultOptions, ...options }; + + const source = resolveAssetSource(image); + + const paletteInstance = await NativeModules.MaterialPalette.createMaterialPalette( + source, + { region, maximumColorCount, - type, - } = { ...defaultOptions, ...options }; - - const source = resolveAssetSource(image); - - const paletteInstance = await NativeModules.MaterialPalette.createMaterialPalette( - source, - { - region, - maximumColorCount, - type: typeof type === 'string' ? [type] : type, - }, - ); - Object.keys(paletteInstance).forEach((profile: ColorProfile) => { - if (isEqual(paletteInstance[profile], defaultLightSwatch)) { - paletteInstance[profile] = null; - } - }); - return paletteInstance; - }, - PaletteProvider: MaterialPaletteProvider, - withPalette: withMaterialPalette, -}; + type: typeof type === 'string' ? [type] : type, + }, + ); + Object.keys(paletteInstance).forEach((profile: ColorProfile) => { + if (isEqual(paletteInstance[profile], defaultLightSwatch)) { + paletteInstance[profile] = null; + } + }); + return paletteInstance; +} -export default namespace; +export const MaterialPaletteProvider = PaletteProvider; +export const withMaterialPalette = withPalette; From b857638f1a0040ccc339c91cf893c3228b3236b3 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 12:58:50 +0200 Subject: [PATCH 11/27] this.props.defaults validator function --- src/PaletteProvider.js | 13 +++--------- src/utils/validate.js | 48 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index a3d5f4a..0a9bf9b 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import createEventEmitter from './createEventEmitter'; import { createMaterialPalette } from './index'; import { defaultSwatches } from './constants/defaults'; +import { validateDefaults } from './utils/validate'; import type { PaletteInstance, @@ -132,18 +133,10 @@ export default class MaterialPaletteProvider }; } - _validateDefaults() { + componentWillMount() { if (this.props.defaults) { - if (typeof this.props.defaults !== 'object') { - throw new Error('this.props.defaults should be an object'); - } else { - // TODO validate defaults - } + validateDefaults(this.props.defaults); } - } - - componentWillMount() { - this._validateDefaults(); execIfFunction(this.props.onInit); createMaterialPalette(this.props.image, this.props.options) .then((palette: PaletteInstance) => { diff --git a/src/utils/validate.js b/src/utils/validate.js index f8538f9..1186eb3 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -1,6 +1,12 @@ // @flow -import type { Image, Options, Region, ColorProfile } from '../types'; +import type { + Image, + Options, + Region, + ColorProfile, + PaletteDefaults, +} from '../types'; export const INVALID_IMAGE_MESSAGE = 'Invalid image param, you should either require a local asset, or provide an external URI'; @@ -98,3 +104,43 @@ export function validateType(type: ColorProfile | Array) { }); } } + +export function validateDefaults(defaults?: PaletteDefaults) { + if (typeof defaults !== 'object') { + throw new Error(`this.props.defaults should be an object`); + } else { + const validProfiles: Array = [ + 'vibrant', + 'lightVibrant', + 'darkVibrant', + 'muted', + 'lightMuted', + 'darkMuted', + ]; + const validProfilesKeys = ['bodyTextColor', 'color', 'titleTextColor']; + Object.keys(defaults).forEach((profile: ColorProfile) => { + if (!validProfiles.includes(profile)) { + throw new Error( + `${profile} is not a valid color profile for this.props.defaults. Please refer to the API documentation`, + ); + } else { + const profileKeys = Object.keys(defaults[profile]).sort(); + const areTypesCorrect = profileKeys.every( + (key: string) => typeof defaults[profile][key] === 'string', + ); + const areEqual = validProfilesKeys.length === profileKeys.length && + validProfilesKeys.every((v: *, i: *) => v === profileKeys[i]); + if (!areEqual) { + throw new Error( + `Each default profile should define 'bodyTextColor', 'color' and 'titleTextColor' parameters. Please refer to the API documentation`, + ); + } + if (!areTypesCorrect) { + throw new Error( + `'bodyTextColor', 'color' and 'titleTextColor' should all be strings`, + ); + } + } + }); + } +} From 49f54951c511c2f79fab044f238d8e67ea3532c3 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 13:13:29 +0200 Subject: [PATCH 12/27] Unit tests for validateDefaults --- src/constants/defaults.js | 15 ++++++++ src/utils/__tests__/validate.test.js | 53 +++++++++++++++++++++++++++- src/utils/validate.js | 13 ++----- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/constants/defaults.js b/src/constants/defaults.js index 232e9f7..c2801e4 100644 --- a/src/constants/defaults.js +++ b/src/constants/defaults.js @@ -9,6 +9,15 @@ const defaultMuted = '#9E9E9E'; const defaultLightMuted = '#BDBDBD'; const defaultDarkMuted = '#616161'; +export const validColorProfiles = [ + 'vibrant', + 'lightVibrant', + 'darkVibrant', + 'muted', + 'lightMuted', + 'darkMuted', +]; + export const defaultRegion: Region = { top: 0, right: 0, @@ -29,6 +38,12 @@ export const defaultLightSwatch: Swatch = { titleTextColor: '#000000', }; +export const defaultProfile = { + color: '#000000', + bodyTextColor: '#000000', + titleTextColor: '#000000', +}; + const defaultDarkSwatch: Swatch = { population: 0, color: '#000000', diff --git a/src/utils/__tests__/validate.test.js b/src/utils/__tests__/validate.test.js index 1720a77..3a7e46b 100644 --- a/src/utils/__tests__/validate.test.js +++ b/src/utils/__tests__/validate.test.js @@ -7,8 +7,9 @@ import validate, { validateRegion, validateMaximumColorCount, validateType, + validateDefaults, } from '../validate'; -import { defaultOptions } from '../../constants/defaults'; +import { defaultOptions, defaultProfile } from '../../constants/defaults'; const VALID_IMAGE = { uri: 'https://something.image.jpg' }; @@ -94,4 +95,54 @@ describe('validation', () => { expect(() => validateType('vibrant')).not.toThrow(); expect(() => validateType(['vibrant', 'lightVibrant'])).not.toThrow(); }); + + it('this.props.defaults validation', () => { + const invalidDefaults1 = 'hej'; + const invalidDefaults2 = { + foo: 'bar', + vibrant: 'bar', + }; + const invalidDefaults3 = { + lightVibrant: 'foo', + muted: { + color: '#000000', + bodyTextColor: '#000000', + titleTextColor: '#000000', + }, + }; + const invalidDefaults4 = { + muted: { + color: '#000000', + bodyTextColor: '#000000', + titleTextColor: '#000000', + }, + darkVibrant: { + color: '#000000', + bodyTextColor: 12, + titleTextColor: '#000000', + }, + }; + const validDefaults = { + muted: defaultProfile, + darkMuted: defaultProfile, + lightMuted: defaultProfile, + darkVibrant: defaultProfile, + vibrant: defaultProfile, + lightVibrant: defaultProfile, + }; + + expect(() => validateDefaults(invalidDefaults1)).toThrow( + 'this.props.defaults should be an object', + ); + expect(() => validateDefaults(invalidDefaults2)).toThrow( + 'foo is not a valid color profile for this.props.defaults. Please refer to the API documentation', + ); + expect(() => validateDefaults(invalidDefaults3)).toThrow( + `Each default profile should define 'bodyTextColor', 'color' and 'titleTextColor' parameters. Please refer to the API documentation`, + ); + expect(() => validateDefaults(invalidDefaults4)).toThrow( + `'bodyTextColor', 'color' and 'titleTextColor' should all be strings`, + ); + expect(() => validateDefaults(validDefaults)).not.toThrow(); + }); }); diff --git a/src/utils/validate.js b/src/utils/validate.js index 1186eb3..74b47ef 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -1,5 +1,6 @@ // @flow +import { validColorProfiles } from '../constants/defaults'; import type { Image, Options, @@ -107,19 +108,11 @@ export function validateType(type: ColorProfile | Array) { export function validateDefaults(defaults?: PaletteDefaults) { if (typeof defaults !== 'object') { - throw new Error(`this.props.defaults should be an object`); + throw new Error('this.props.defaults should be an object'); } else { - const validProfiles: Array = [ - 'vibrant', - 'lightVibrant', - 'darkVibrant', - 'muted', - 'lightMuted', - 'darkMuted', - ]; const validProfilesKeys = ['bodyTextColor', 'color', 'titleTextColor']; Object.keys(defaults).forEach((profile: ColorProfile) => { - if (!validProfiles.includes(profile)) { + if (!validColorProfiles.includes(profile)) { throw new Error( `${profile} is not a valid color profile for this.props.defaults. Please refer to the API documentation`, ); From 7a92c47702002f7756d56e5e7e16d81ba4a1bc48 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 14:26:33 +0200 Subject: [PATCH 13/27] Fixing unit tests --- src/PaletteProvider.js | 5 +-- src/__tests__/PaletteProvider.test.js | 50 +++++++++++++-------- src/__tests__/withPalette.test.js | 64 +-------------------------- 3 files changed, 34 insertions(+), 85 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 0a9bf9b..2c5c6c2 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -45,10 +45,7 @@ type Props = { /** * Finish handler, called right after the palette is generated */ - onFinish?: ( - palette: PaletteInstance, - globalDefaults: PaletteDefaults, - ) => void, + onFinish?: (palette: PaletteInstance) => void, /** * Render the children regardless whether palette is still being created, does not * take effect if `LoaderComponent` is specified diff --git a/src/__tests__/PaletteProvider.test.js b/src/__tests__/PaletteProvider.test.js index 693d03c..f2a811b 100644 --- a/src/__tests__/PaletteProvider.test.js +++ b/src/__tests__/PaletteProvider.test.js @@ -1,13 +1,14 @@ /* eslint flowtype/require-parameter-type: 0 */ /* eslint-disable import/first */ -jest.mock('../index.js', () => ({ create: jest.fn() })); +jest.mock('../index.js', () => ({ createMaterialPalette: jest.fn() })); import React from 'react'; import { Text } from 'react-native'; import PropTypes from 'prop-types'; import { shallow, render } from 'enzyme'; import PaletteProvider, { KEY } from '../PaletteProvider'; -import MaterialPalette from '../index'; +import { createMaterialPalette } from '../index'; +import { defaultSwatches } from '../constants/defaults'; // eslint-disable-next-line react/prefer-stateless-function class TestComponent extends React.Component { @@ -23,19 +24,26 @@ class TestComponent extends React.Component { describe('PaletteProvider', () => { beforeEach(() => { - MaterialPalette.create.mockReset(); + createMaterialPalette.mockReset(); }); it('should create palette and call `onInit` and `onFinish` handlers', done => { - MaterialPalette.create.mockImplementation(() => + createMaterialPalette.mockImplementation(() => Promise.resolve({ vibrant: null })); - function onFinish(palette, defaults) { - expect(MaterialPalette.create).toHaveBeenCalledWith(0, { + function onFinish(palette) { + expect(createMaterialPalette).toHaveBeenCalledWith(0, { type: 'vibrant', }); - expect(palette).toEqual({ vibrant: null }); - expect(defaults).toEqual({ vibrant: { color: '#000000' } }); + expect(palette).toEqual({ + ...defaultSwatches, + vibrant: { + color: '#000000', + bodyTextColor: '#FFFFFF', + titleTextColor: '#FFFFFF', + population: 0, + }, + }); done(); } @@ -44,7 +52,13 @@ describe('PaletteProvider', () => { image={0} options={{ type: 'vibrant' }} onFinish={onFinish} - defaults={{ vibrant: { color: '#000000' } }} + defaults={{ + vibrant: { + color: '#000000', + bodyTextColor: '#FFFFFF', + titleTextColor: '#FFFFFF', + }, + }} > Test , @@ -52,7 +66,7 @@ describe('PaletteProvider', () => { }); it('should pass `subscribe` function via context', done => { - MaterialPalette.create.mockImplementation(() => + createMaterialPalette.mockImplementation(() => Promise.resolve({ vibrant: null })); function onRender(context) { @@ -72,7 +86,7 @@ describe('PaletteProvider', () => { }); it('should run `onError` handler if palette creation fails', done => { - MaterialPalette.create.mockImplementation(() => + createMaterialPalette.mockImplementation(() => Promise.reject(new Error('test'))); function onError(error) { @@ -98,7 +112,7 @@ describe('PaletteProvider', () => { resolve(); } - MaterialPalette.create.mockImplementation(() => ({ + createMaterialPalette.mockImplementation(() => ({ then() { return this; }, @@ -119,7 +133,7 @@ describe('PaletteProvider', () => { })); it('should render children if `forceRender` is true when creating palette', done => { - MaterialPalette.create.mockImplementation( + createMaterialPalette.mockImplementation( () => new Promise(resolve => { setTimeout( @@ -131,16 +145,16 @@ describe('PaletteProvider', () => { }), ); - let firstNatification = true; + let firstNotification = true; function onRender(context) { setTimeout( () => { context[KEY](data => { - if (firstNatification) { - firstNatification = false; + if (firstNotification) { + firstNotification = false; expect(data).toBeNull(); } else { - expect(data.palette.vibrant).toEqual({}); + expect(data.palette.vibrant).toEqual(defaultSwatches.vibrant); done(); } }); @@ -159,7 +173,7 @@ describe('PaletteProvider', () => { }); it('should render component specified in `waitForPalette` when creating palette', () => { - MaterialPalette.create.mockImplementation(() => new Promise(() => {})); + createMaterialPalette.mockImplementation(() => new Promise(() => {})); const wrapper = shallow( { wrapper.shallow(); }); - it('should merge palette with global defaults', () => { - const defaults = { - lightVibrant: { - color: '#f1f1f1', - bodyTextColor: '#000000', - titleTextColor: '#000000', - }, - }; - let subscriber = jest.fn(); - function onFirstRender(palette, style) { - expect(palette).toEqual({}); - expect(style).toEqual([undefined, {}]); - } - function onSecondRender(palette, style) { - expect(palette).toEqual({ - ...paletteMock, - lightVibrant: { population: 0, ...defaults.lightVibrant }, - }); - expect(style).toEqual([ - undefined, - { color: defaults.lightVibrant.color }, - ]); - } - - const PaletteTest = withPalette(palette => ({ - color: palette.lightVibrant && palette.lightVibrant.color, - }))(getTestComponent()); - const wrapper = shallow( - , - { - context: createContext(fn => { - subscriber = fn; - }), - }, - ); - - wrapper.shallow(); - subscriber({ - palette: paletteMock, - globalDefaults: defaults, - }); - wrapper.shallow(); - }); - - it('should merge palette with both global and local defaults and style prop', () => { - const globalDefaults = { - lightVibrant: { - color: '#f1f1f1', - bodyTextColor: '#000000', - titleTextColor: '#000000', - }, - muted: { - color: '#f1f1f1', - bodyTextColor: '#000000', - titleTextColor: '#000000', - }, - }; + it('should merge palette with local defaults and style prop', () => { const localDefaults = { lightVibrant: { color: '#a4a4a4', @@ -225,13 +166,11 @@ describe('withPalette', () => { expect(palette).toEqual({ ...paletteMock, lightVibrant: { population: 0, ...localDefaults.lightVibrant }, - muted: { population: 0, ...globalDefaults.muted }, }); expect(style).toEqual([ { fontSize: '14px' }, { color: localDefaults.lightVibrant.color, - backgroundColor: globalDefaults.muted.color, }, ]); } @@ -259,7 +198,6 @@ describe('withPalette', () => { wrapper.shallow(); subscriber({ palette: paletteMock, - globalDefaults, }); wrapper.shallow(); }); From b1434b4b025efe25b5223b84b850bdf657ce9531 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 14:32:36 +0200 Subject: [PATCH 14/27] Disabling require flow parameter type rule --- package.json | 2 +- src/utils/validate.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ff23440..57a3576 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "boolean" ], "flowtype/no-weak-types": 1, - "flowtype/require-parameter-type": 2, + "flowtype/require-parameter-type": 0, "flowtype/require-return-type": [ 0, "always", diff --git a/src/utils/validate.js b/src/utils/validate.js index 74b47ef..19a9fd7 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -106,12 +106,12 @@ export function validateType(type: ColorProfile | Array) { } } -export function validateDefaults(defaults?: PaletteDefaults) { +export function validateDefaults(defaults: PaletteDefaults) { if (typeof defaults !== 'object') { throw new Error('this.props.defaults should be an object'); } else { const validProfilesKeys = ['bodyTextColor', 'color', 'titleTextColor']; - Object.keys(defaults).forEach((profile: ColorProfile) => { + Object.keys(defaults).forEach(profile => { if (!validColorProfiles.includes(profile)) { throw new Error( `${profile} is not a valid color profile for this.props.defaults. Please refer to the API documentation`, @@ -119,10 +119,10 @@ export function validateDefaults(defaults?: PaletteDefaults) { } else { const profileKeys = Object.keys(defaults[profile]).sort(); const areTypesCorrect = profileKeys.every( - (key: string) => typeof defaults[profile][key] === 'string', + key => typeof defaults[profile][key] === 'string', ); const areEqual = validProfilesKeys.length === profileKeys.length && - validProfilesKeys.every((v: *, i: *) => v === profileKeys[i]); + validProfilesKeys.every((v, i) => v === profileKeys[i]); if (!areEqual) { throw new Error( `Each default profile should define 'bodyTextColor', 'color' and 'titleTextColor' parameters. Please refer to the API documentation`, From 6f5926840ff61f35b3604c1b9b842c74251a3060 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 14:50:37 +0200 Subject: [PATCH 15/27] Defaults should only be provied for the types specified by the user --- src/PaletteProvider.js | 12 +++++++++++- src/__tests__/PaletteProvider.test.js | 10 ++-------- src/constants/defaults.js | 2 +- src/utils/validate.js | 2 +- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 2c5c6c2..e349525 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -99,8 +99,18 @@ export default class MaterialPaletteProvider } _mergeWithDefaults(palette: PaletteInstance) { + const globalDefaultsForTypesProvided = ((Object.keys( + palette, + ): any): ColorProfile[]).reduce( + (acc, profile) => ({ + ...acc, + [profile]: defaultSwatches[profile], + }), + {}, + ); + const defaults = { - ...defaultSwatches, + ...globalDefaultsForTypesProvided, ...((Object.keys( this.props.defaults || {}, ): any): ColorProfile[]).reduce( diff --git a/src/__tests__/PaletteProvider.test.js b/src/__tests__/PaletteProvider.test.js index f2a811b..b26acdb 100644 --- a/src/__tests__/PaletteProvider.test.js +++ b/src/__tests__/PaletteProvider.test.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import { shallow, render } from 'enzyme'; import PaletteProvider, { KEY } from '../PaletteProvider'; import { createMaterialPalette } from '../index'; -import { defaultSwatches } from '../constants/defaults'; +import { defaultSwatches, defaultDarkSwatch } from '../constants/defaults'; // eslint-disable-next-line react/prefer-stateless-function class TestComponent extends React.Component { @@ -36,13 +36,7 @@ describe('PaletteProvider', () => { type: 'vibrant', }); expect(palette).toEqual({ - ...defaultSwatches, - vibrant: { - color: '#000000', - bodyTextColor: '#FFFFFF', - titleTextColor: '#FFFFFF', - population: 0, - }, + vibrant: defaultDarkSwatch, }); done(); } diff --git a/src/constants/defaults.js b/src/constants/defaults.js index c2801e4..d6e12e5 100644 --- a/src/constants/defaults.js +++ b/src/constants/defaults.js @@ -44,7 +44,7 @@ export const defaultProfile = { titleTextColor: '#000000', }; -const defaultDarkSwatch: Swatch = { +export const defaultDarkSwatch: Swatch = { population: 0, color: '#000000', bodyTextColor: '#FFFFFF', diff --git a/src/utils/validate.js b/src/utils/validate.js index 19a9fd7..242d04d 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -111,7 +111,7 @@ export function validateDefaults(defaults: PaletteDefaults) { throw new Error('this.props.defaults should be an object'); } else { const validProfilesKeys = ['bodyTextColor', 'color', 'titleTextColor']; - Object.keys(defaults).forEach(profile => { + (Object.keys((defaults: any)): ColorProfile[]).forEach(profile => { if (!validColorProfiles.includes(profile)) { throw new Error( `${profile} is not a valid color profile for this.props.defaults. Please refer to the API documentation`, From 0f1c7c1d0bd718d696648c48e6076ceca4e578b3 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 15:00:25 +0200 Subject: [PATCH 16/27] Separating createMaterialPalette into its own module --- src/createMaterialPalette.js | 38 ++++++++++++++++++++++++++++++ src/index.js | 45 +++--------------------------------- 2 files changed, 41 insertions(+), 42 deletions(-) create mode 100644 src/createMaterialPalette.js diff --git a/src/createMaterialPalette.js b/src/createMaterialPalette.js new file mode 100644 index 0000000..c21d6db --- /dev/null +++ b/src/createMaterialPalette.js @@ -0,0 +1,38 @@ +/* @flow */ + +import { NativeModules } from 'react-native'; +import resolveAssetSource + from 'react-native/Libraries/Image/resolveAssetSource'; +import isEqual from 'lodash/isEqual'; +import { defaultOptions, defaultLightSwatch } from './constants/defaults'; +import validate from './utils/validate'; +import type { Image, PaletteInstance, Options, ColorProfile } from './types'; + +export default (async function createMaterialPalette( + image: Image, + options?: Options = defaultOptions, +): Promise { + validate(image, options); + const { + region, + maximumColorCount, + type, + } = { ...defaultOptions, ...options }; + + const source = resolveAssetSource(image); + + const paletteInstance = await NativeModules.MaterialPalette.createMaterialPalette( + source, + { + region, + maximumColorCount, + type: typeof type === 'string' ? [type] : type, + }, + ); + Object.keys(paletteInstance).forEach((profile: ColorProfile) => { + if (isEqual(paletteInstance[profile], defaultLightSwatch)) { + paletteInstance[profile] = null; + } + }); + return paletteInstance; +}); diff --git a/src/index.js b/src/index.js index 15949fe..3c6f318 100644 --- a/src/index.js +++ b/src/index.js @@ -1,44 +1,5 @@ /* @flow */ -import { NativeModules } from 'react-native'; -import resolveAssetSource - from 'react-native/Libraries/Image/resolveAssetSource'; -import isEqual from 'lodash/isEqual'; -import { defaultOptions, defaultLightSwatch } from './constants/defaults'; -import validate from './utils/validate'; -import type { Image, PaletteInstance, Options, ColorProfile } from './types'; - -import PaletteProvider from './PaletteProvider'; -import withPalette from './withPalette'; - -export async function createMaterialPalette( - image: Image, - options?: Options = defaultOptions, -): Promise { - validate(image, options); - const { - region, - maximumColorCount, - type, - } = { ...defaultOptions, ...options }; - - const source = resolveAssetSource(image); - - const paletteInstance = await NativeModules.MaterialPalette.createMaterialPalette( - source, - { - region, - maximumColorCount, - type: typeof type === 'string' ? [type] : type, - }, - ); - Object.keys(paletteInstance).forEach((profile: ColorProfile) => { - if (isEqual(paletteInstance[profile], defaultLightSwatch)) { - paletteInstance[profile] = null; - } - }); - return paletteInstance; -} - -export const MaterialPaletteProvider = PaletteProvider; -export const withMaterialPalette = withPalette; +export { default as MaterialPaletteProvider } from './PaletteProvider'; +export { default as withPalette } from './withPalette'; +export { default as createMaterialPalette } from './createMaterialPalette'; From c8de2409267ce24a11b5dab4ce6e707901474753 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 15:47:12 +0200 Subject: [PATCH 17/27] Unit test for PaletteProvider merge with defaults --- src/PaletteProvider.js | 2 +- src/__tests__/PaletteProvider.test.js | 113 +++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index e349525..977b4e7 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -127,6 +127,7 @@ export default class MaterialPaletteProvider ), }; return { + ...defaults, ...((Object.keys(palette): any): ColorProfile[]) .filter((profile: ColorProfile) => !!palette[profile]) // Stripping out unavailable profiles .reduce( @@ -136,7 +137,6 @@ export default class MaterialPaletteProvider }), {}, ), - ...defaults, }; } diff --git a/src/__tests__/PaletteProvider.test.js b/src/__tests__/PaletteProvider.test.js index b26acdb..a29ece8 100644 --- a/src/__tests__/PaletteProvider.test.js +++ b/src/__tests__/PaletteProvider.test.js @@ -148,7 +148,7 @@ describe('PaletteProvider', () => { firstNotification = false; expect(data).toBeNull(); } else { - expect(data.palette.vibrant).toEqual(defaultSwatches.vibrant); + expect(data.palette.vibrant).toEqual({}); done(); } }); @@ -179,4 +179,115 @@ describe('PaletteProvider', () => { ); expect(wrapper.shallow().props().children).toEqual('Loading'); }); + + describe('Merge with defaults', () => { + const PaletteWrapper = ({ types, defaults, onFinish }) => ( + + Test + + ); + + it('should merge palette with globals when props.defaults is not provided, for the types specified', done => { + createMaterialPalette.mockImplementation(() => + Promise.resolve({ + vibrant: { + color: 'green', + bodyTextColor: 'red', + titleTextColor: 'red', + population: 20, + }, + muted: null, + })); + + function onFinish(palette) { + expect(palette).toEqual({ + vibrant: { + color: 'green', + bodyTextColor: 'red', + titleTextColor: 'red', + population: 20, + }, + muted: defaultSwatches.muted, + }); + done(); + } + + render( + , + ); + }); + + it('should merge palette with both globals and local defaults, for the types specified', done => { + createMaterialPalette.mockImplementation(() => + Promise.resolve({ + muted: { + color: 'green', + bodyTextColor: 'red', + titleTextColor: 'red', + population: 20, + }, + darkMuted: { + color: 'yellow', + bodyTextColor: 'blue', + titleTextColor: 'blue', + population: 40, + }, + lightVibrant: null, + darkVibrant: null, + })); + + function onFinish(palette) { + expect(palette).toEqual({ + muted: { + color: 'green', + bodyTextColor: 'red', + titleTextColor: 'red', + population: 20, + }, + darkMuted: { + color: 'yellow', + bodyTextColor: 'blue', + titleTextColor: 'blue', + population: 40, + }, + lightVibrant: { + color: 'orange', + bodyTextColor: 'purple', + titleTextColor: 'purple', + population: 0, + }, + darkVibrant: defaultSwatches.darkVibrant, + }); + done(); + } + + render( + , + ); + }); + }); }); From 3b70ee79c52e1be5c753dbed9436ea3c05a4a770 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 15:53:39 +0200 Subject: [PATCH 18/27] Not duplicating logic --- src/constants/defaults.js | 16 ++++++++-------- src/types.js | 17 +++-------------- src/utils/validate.js | 2 +- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/constants/defaults.js b/src/constants/defaults.js index d6e12e5..dbb807c 100644 --- a/src/constants/defaults.js +++ b/src/constants/defaults.js @@ -9,14 +9,14 @@ const defaultMuted = '#9E9E9E'; const defaultLightMuted = '#BDBDBD'; const defaultDarkMuted = '#616161'; -export const validColorProfiles = [ - 'vibrant', - 'lightVibrant', - 'darkVibrant', - 'muted', - 'lightMuted', - 'darkMuted', -]; +export const validColorProfiles = { + vibrant: true, + lightVibrant: true, + darkVibrant: true, + muted: true, + lightMuted: true, + darkMuted: true, +}; export const defaultRegion: Region = { top: 0, diff --git a/src/types.js b/src/types.js index 57ddada..17c63d9 100644 --- a/src/types.js +++ b/src/types.js @@ -1,5 +1,7 @@ /* @flow */ +import { validColorProfiles } from './constants/defaults'; + // Number is the opaque type returned by require('./image.jpg') export type Image = number | { uri: string }; export type Region = { @@ -9,14 +11,7 @@ export type Region = { right: number, }; -export type ColorProfile = - | 'muted' - | 'vibrant' - | 'darkMuted' - | 'darkVibrant' - | 'lightMuted' - | 'lightVibrant'; - +export type ColorProfile = $Keys; export type Swatch = { population: number, // number of pixels color: string, // color for swatch, @@ -34,12 +29,6 @@ export type PaletteDefaults = { [key: ColorProfile]: DefaultSwatch, }; -export type SwatchColors = { - color?: string, - bodyTextColor?: string, - titleTextColor?: string, -}; - export type PaletteInstance = { [key: ColorProfile]: ?Swatch, }; diff --git a/src/utils/validate.js b/src/utils/validate.js index 242d04d..9a3a0e6 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -112,7 +112,7 @@ export function validateDefaults(defaults: PaletteDefaults) { } else { const validProfilesKeys = ['bodyTextColor', 'color', 'titleTextColor']; (Object.keys((defaults: any)): ColorProfile[]).forEach(profile => { - if (!validColorProfiles.includes(profile)) { + if (!Object.keys(validColorProfiles).includes(profile)) { throw new Error( `${profile} is not a valid color profile for this.props.defaults. Please refer to the API documentation`, ); From 41c95bd089f19c08ea877da17c3f74cba8043aed Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Tue, 1 Aug 2017 16:43:15 +0200 Subject: [PATCH 19/27] Unit test for createMaterialPalette --- src/__tests__/PaletteProvider.test.js | 1 - src/__tests__/createEventEmitter.test.js | 1 - src/__tests__/createMaterialPalette.test.js | 48 +++++++++++++++++++++ src/__tests__/index.test.js | 9 ---- src/__tests__/withPalette.test.js | 1 - src/utils/validate.js | 2 +- 6 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 src/__tests__/createMaterialPalette.test.js delete mode 100644 src/__tests__/index.test.js diff --git a/src/__tests__/PaletteProvider.test.js b/src/__tests__/PaletteProvider.test.js index a29ece8..6b490ed 100644 --- a/src/__tests__/PaletteProvider.test.js +++ b/src/__tests__/PaletteProvider.test.js @@ -1,4 +1,3 @@ -/* eslint flowtype/require-parameter-type: 0 */ /* eslint-disable import/first */ jest.mock('../index.js', () => ({ createMaterialPalette: jest.fn() })); diff --git a/src/__tests__/createEventEmitter.test.js b/src/__tests__/createEventEmitter.test.js index f46f226..aa2077f 100644 --- a/src/__tests__/createEventEmitter.test.js +++ b/src/__tests__/createEventEmitter.test.js @@ -1,4 +1,3 @@ -/* eslint flowtype/require-parameter-type: 0 */ import createEventEmitter from '../createEventEmitter'; describe('createEventEmitter', () => { diff --git a/src/__tests__/createMaterialPalette.test.js b/src/__tests__/createMaterialPalette.test.js new file mode 100644 index 0000000..0eed6a9 --- /dev/null +++ b/src/__tests__/createMaterialPalette.test.js @@ -0,0 +1,48 @@ +/* eslint-disable import/first */ +jest.mock('react-native', () => ({ + NativeModules: { + MaterialPalette: { + createMaterialPalette: jest.fn(), + }, + }, +})); +jest.mock('react-native/Libraries/Image/resolveAssetSource', () => jest.fn()); +jest.mock('../utils/validate', () => jest.fn()); + +import { NativeModules } from 'react-native'; +import resolveAssetSource + from 'react-native/Libraries/Image/resolveAssetSource'; +import createPalette from '../createMaterialPalette'; +import { defaultLightSwatch } from '../constants/defaults'; + +describe('createMaterialPalette', () => { + beforeEach(() => { + NativeModules.MaterialPalette.createMaterialPalette.mockReset(); + resolveAssetSource.mockReset(); + }); + + it('should provide null for the profiles not available', () => { + NativeModules.MaterialPalette.createMaterialPalette.mockImplementation(() => + Promise.resolve({ + vibrant: defaultLightSwatch, + darkMuted: { + color: 'green', + population: 20, + bodyTextColor: 'red', + titleTextColor: 'red', + }, + })); + + createPalette({ + image: 0, + }).then(palette => { + expect(palette.vibrant).toBeNull(); + expect(palette.darkMuted).toEqual({ + color: 'green', + population: 20, + bodyTextColor: 'red', + titleTextColor: 'red', + }); + }); + }); +}); diff --git a/src/__tests__/index.test.js b/src/__tests__/index.test.js deleted file mode 100644 index 019f516..0000000 --- a/src/__tests__/index.test.js +++ /dev/null @@ -1,9 +0,0 @@ -/* eslint-env jest */ - -import { createMaterialPalette } from '../'; - -describe('reactNativeMaterialPalette', () => { - it('should return argument', () => { - expect(typeof createMaterialPalette).toBe('function'); - }); -}); diff --git a/src/__tests__/withPalette.test.js b/src/__tests__/withPalette.test.js index dd2283e..de6d284 100644 --- a/src/__tests__/withPalette.test.js +++ b/src/__tests__/withPalette.test.js @@ -1,4 +1,3 @@ -/* eslint flowtype/require-parameter-type: 0 */ import React from 'react'; import { shallow } from 'enzyme'; import withPalette from '../withPalette'; diff --git a/src/utils/validate.js b/src/utils/validate.js index 9a3a0e6..32b8dfd 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -112,7 +112,7 @@ export function validateDefaults(defaults: PaletteDefaults) { } else { const validProfilesKeys = ['bodyTextColor', 'color', 'titleTextColor']; (Object.keys((defaults: any)): ColorProfile[]).forEach(profile => { - if (!Object.keys(validColorProfiles).includes(profile)) { + if (!(profile in validColorProfiles)) { throw new Error( `${profile} is not a valid color profile for this.props.defaults. Please refer to the API documentation`, ); From 95d5ae4d8a13cd6136d57a9c9a1ac2f677aa49be Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 11:27:39 +0200 Subject: [PATCH 20/27] Adding artifacts and fixing async test for palette --- .circleci/config.yml | 3 ++- src/__tests__/createMaterialPalette.test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 94b305d..1d9e52f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,7 +24,8 @@ jobs: - v1-dependencies - run: yarn - + - store_artifacts: + path: ./coverage - save_cache: paths: - node_modules diff --git a/src/__tests__/createMaterialPalette.test.js b/src/__tests__/createMaterialPalette.test.js index 0eed6a9..9288697 100644 --- a/src/__tests__/createMaterialPalette.test.js +++ b/src/__tests__/createMaterialPalette.test.js @@ -33,7 +33,7 @@ describe('createMaterialPalette', () => { }, })); - createPalette({ + return createPalette({ image: 0, }).then(palette => { expect(palette.vibrant).toBeNull(); From f564ad160bb6ceb6c9af479ae1c507cabb4b5d49 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 11:31:05 +0200 Subject: [PATCH 21/27] Trying out different path for coverage --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d9e52f..ca64e2f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,7 +25,7 @@ jobs: - run: yarn - store_artifacts: - path: ./coverage + path: coverage - save_cache: paths: - node_modules From 8054f5bf6f30da80b2efbb6939e46a06dfef487c Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 11:32:17 +0200 Subject: [PATCH 22/27] Trying out different path for coverage --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ca64e2f..4766c93 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,8 +24,6 @@ jobs: - v1-dependencies - run: yarn - - store_artifacts: - path: coverage - save_cache: paths: - node_modules @@ -34,3 +32,5 @@ jobs: # run flow and unit tests! - run: yarn run flow - run: yarn run jest:coverage + - store_artifacts: + path: coverage From 6ba98c4227f4645c9d0226372fc45113ff1729ef Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 14:09:45 +0200 Subject: [PATCH 23/27] 100% test coverage on utils --- src/PaletteProvider.js | 2 +- src/__tests__/createMaterialPalette.test.js | 2 +- src/createMaterialPalette.js | 6 +- .../__tests__/validateCreatePalette.test.js | 56 +++++++++++++++++++ ...t.js => validateCreatePaletteArgs.test.js} | 13 ++--- src/utils/validateCreatePalette.js | 26 +++++++++ ...lidate.js => validateCreatePaletteArgs.js} | 16 +----- 7 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 src/utils/__tests__/validateCreatePalette.test.js rename src/utils/__tests__/{validate.test.js => validateCreatePaletteArgs.test.js} (93%) create mode 100644 src/utils/validateCreatePalette.js rename src/utils/{validate.js => validateCreatePaletteArgs.js} (91%) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index 977b4e7..bd2da39 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import createEventEmitter from './createEventEmitter'; import { createMaterialPalette } from './index'; import { defaultSwatches } from './constants/defaults'; -import { validateDefaults } from './utils/validate'; +import { validateDefaults } from './utils/validateCreatePaletteArgs'; import type { PaletteInstance, diff --git a/src/__tests__/createMaterialPalette.test.js b/src/__tests__/createMaterialPalette.test.js index 9288697..b20c720 100644 --- a/src/__tests__/createMaterialPalette.test.js +++ b/src/__tests__/createMaterialPalette.test.js @@ -7,7 +7,7 @@ jest.mock('react-native', () => ({ }, })); jest.mock('react-native/Libraries/Image/resolveAssetSource', () => jest.fn()); -jest.mock('../utils/validate', () => jest.fn()); +jest.mock('../utils/validateCreatePalette', () => jest.fn()); import { NativeModules } from 'react-native'; import resolveAssetSource diff --git a/src/createMaterialPalette.js b/src/createMaterialPalette.js index c21d6db..700d7f8 100644 --- a/src/createMaterialPalette.js +++ b/src/createMaterialPalette.js @@ -5,14 +5,14 @@ import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import isEqual from 'lodash/isEqual'; import { defaultOptions, defaultLightSwatch } from './constants/defaults'; -import validate from './utils/validate'; +import validateCreatePalette from './utils/validateCreatePalette'; import type { Image, PaletteInstance, Options, ColorProfile } from './types'; export default (async function createMaterialPalette( image: Image, - options?: Options = defaultOptions, + options?: Options = {}, ): Promise { - validate(image, options); + validateCreatePalette(image, options); const { region, maximumColorCount, diff --git a/src/utils/__tests__/validateCreatePalette.test.js b/src/utils/__tests__/validateCreatePalette.test.js new file mode 100644 index 0000000..4d9a5f3 --- /dev/null +++ b/src/utils/__tests__/validateCreatePalette.test.js @@ -0,0 +1,56 @@ +/* eslint-disable import/first */ +jest.mock('../validateCreatePaletteArgs', () => ({ + __esModule: true, + validateImage: jest.fn(), + validateOptionsKeys: jest.fn(), + validateType: jest.fn(), + validateMaximumColorCount: jest.fn(), + validateRegion: jest.fn(), +})); + +import validateCreatePalette from '../validateCreatePalette'; +import { defaultOptions } from '../../constants/defaults'; +import { + validateType, + validateImage, + validateMaximumColorCount, + validateRegion, + validateOptionsKeys, +} from '../validateCreatePaletteArgs'; + +const VALID_IMAGE = { uri: 'https://something.image.jpg' }; + +describe('validateCreatePalette', () => { + beforeEach(() => { + validateType.mockReset(); + validateImage.mockReset(); + validateMaximumColorCount.mockReset(); + validateRegion.mockReset(); + validateOptionsKeys.mockReset(); + }); + + it('should run all validators if all args are passed', () => { + expect(() => + validateCreatePalette(VALID_IMAGE, defaultOptions)).not.toThrow(); + + validateCreatePalette(VALID_IMAGE, defaultOptions); + + expect(validateImage).toHaveBeenCalledWith(VALID_IMAGE); + expect(validateOptionsKeys).toHaveBeenCalledWith(defaultOptions); + expect(validateType).toHaveBeenCalledWith(defaultOptions.type); + expect(validateRegion).toHaveBeenCalledWith(defaultOptions.region); + expect(validateMaximumColorCount).toHaveBeenCalledWith( + defaultOptions.maximumColorCount, + ); + }); + + it('should not run options validators if options are not provided', () => { + expect(() => validateCreatePalette(VALID_IMAGE, {})).not.toThrow(); + validateCreatePalette(VALID_IMAGE, {}); + expect(validateImage).toHaveBeenCalledWith(VALID_IMAGE); + expect(validateOptionsKeys).not.toHaveBeenCalled(); + expect(validateType).not.toHaveBeenCalled(); + expect(validateRegion).not.toHaveBeenCalled(); + expect(validateMaximumColorCount).not.toHaveBeenCalled(); + }); +}); diff --git a/src/utils/__tests__/validate.test.js b/src/utils/__tests__/validateCreatePaletteArgs.test.js similarity index 93% rename from src/utils/__tests__/validate.test.js rename to src/utils/__tests__/validateCreatePaletteArgs.test.js index 3a7e46b..fc489b7 100644 --- a/src/utils/__tests__/validate.test.js +++ b/src/utils/__tests__/validateCreatePaletteArgs.test.js @@ -1,5 +1,4 @@ -/* eslint flowtype/require-parameter-type: 0 */ -import validate, { +import { INVALID_IMAGE_MESSAGE, createOptionsErrorMessage, validateImage, @@ -8,16 +7,12 @@ import validate, { validateMaximumColorCount, validateType, validateDefaults, -} from '../validate'; -import { defaultOptions, defaultProfile } from '../../constants/defaults'; +} from '../validateCreatePaletteArgs'; +import { defaultProfile } from '../../constants/defaults'; const VALID_IMAGE = { uri: 'https://something.image.jpg' }; -describe('validation', () => { - it('should run all validators if all args are passed', () => { - expect(() => validate(VALID_IMAGE, defaultOptions)).not.toThrow(); - }); - +describe('validateCreatePaletteArgs', () => { it('Should throw if image param is not valid', () => { expect(() => validateImage(false)).toThrow(INVALID_IMAGE_MESSAGE); expect(() => validateImage({ urii: '' })).toThrow(INVALID_IMAGE_MESSAGE); diff --git a/src/utils/validateCreatePalette.js b/src/utils/validateCreatePalette.js new file mode 100644 index 0000000..fd05f09 --- /dev/null +++ b/src/utils/validateCreatePalette.js @@ -0,0 +1,26 @@ +/* @flow */ + +import { + validateImage, + validateOptionsKeys, + validateType, + validateMaximumColorCount, + validateRegion, +} from './validateCreatePaletteArgs'; +import type { Image, Options } from '../types'; + +export default function validateCreatePalette(image: Image, options: Options) { + validateImage(image); + if (Object.keys(options).length) { + validateOptionsKeys(options); + } + if (options.type) { + validateType(options.type); + } + if (options.maximumColorCount) { + validateMaximumColorCount(options.maximumColorCount); + } + if (options.region) { + validateRegion(options.region); + } +} diff --git a/src/utils/validate.js b/src/utils/validateCreatePaletteArgs.js similarity index 91% rename from src/utils/validate.js rename to src/utils/validateCreatePaletteArgs.js index 32b8dfd..9782201 100644 --- a/src/utils/validate.js +++ b/src/utils/validateCreatePaletteArgs.js @@ -1,4 +1,4 @@ -// @flow +/* @flow */ import { validColorProfiles } from '../constants/defaults'; import type { @@ -14,20 +14,6 @@ export const INVALID_IMAGE_MESSAGE = 'Invalid image param, you should either req export const createOptionsErrorMessage = (hint: string): string => `Invalid options param - ${hint}. Please refer to the API documentation`; -export default function validate(image: Image, options: Options) { - validateImage(image); - validateOptionsKeys(options); - if (options.type) { - validateType(options.type); - } - if (options.maximumColorCount) { - validateMaximumColorCount(options.maximumColorCount); - } - if (options.region) { - validateRegion(options.region); - } -} - export function validateImage(image: Image) { if (typeof image !== 'number' && typeof image !== 'object') { throw new Error(INVALID_IMAGE_MESSAGE); From 1578bf531ee3479af3d5d50e9b0ee03e480ea59d Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 14:24:11 +0200 Subject: [PATCH 24/27] 100% test coverage for createMaterialPalette --- src/__tests__/createMaterialPalette.test.js | 41 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/__tests__/createMaterialPalette.test.js b/src/__tests__/createMaterialPalette.test.js index b20c720..ed52f8b 100644 --- a/src/__tests__/createMaterialPalette.test.js +++ b/src/__tests__/createMaterialPalette.test.js @@ -13,7 +13,8 @@ import { NativeModules } from 'react-native'; import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import createPalette from '../createMaterialPalette'; -import { defaultLightSwatch } from '../constants/defaults'; +import validateCreatePalette from '../utils/validateCreatePalette'; +import { defaultLightSwatch, defaultOptions } from '../constants/defaults'; describe('createMaterialPalette', () => { beforeEach(() => { @@ -21,10 +22,28 @@ describe('createMaterialPalette', () => { resolveAssetSource.mockReset(); }); - it('should provide null for the profiles not available', () => { + it('should create palette with default options if no options are provided', () => { NativeModules.MaterialPalette.createMaterialPalette.mockImplementation(() => Promise.resolve({ vibrant: defaultLightSwatch, + })); + resolveAssetSource.mockImplementation(() => `file://asset.jpg`); + + return createPalette(0).then(() => { + expect( + NativeModules.MaterialPalette.createMaterialPalette, + ).toHaveBeenCalledWith(`file://asset.jpg`, { + type: ['vibrant'], + region: defaultOptions.region, + maximumColorCount: defaultOptions.maximumColorCount, + }); + }); + }); + + it('should call all the proper methods and provide null for the profiles not available', () => { + NativeModules.MaterialPalette.createMaterialPalette.mockImplementation(() => + Promise.resolve({ + lightVibrant: defaultLightSwatch, darkMuted: { color: 'green', population: 20, @@ -32,11 +51,23 @@ describe('createMaterialPalette', () => { titleTextColor: 'red', }, })); + resolveAssetSource.mockImplementation(() => `file://asset.jpg`); - return createPalette({ - image: 0, + return createPalette(0, { + type: ['lightVibrant', 'darkMuted'], }).then(palette => { - expect(palette.vibrant).toBeNull(); + expect(validateCreatePalette).toHaveBeenCalledWith(0, { + type: ['lightVibrant', 'darkMuted'], + }); + expect(resolveAssetSource).toHaveBeenCalledWith(0); + expect( + NativeModules.MaterialPalette.createMaterialPalette, + ).toHaveBeenCalledWith(`file://asset.jpg`, { + type: ['lightVibrant', 'darkMuted'], + region: defaultOptions.region, + maximumColorCount: defaultOptions.maximumColorCount, + }); + expect(palette.lightVibrant).toBeNull(); expect(palette.darkMuted).toEqual({ color: 'green', population: 20, From 57e157604e0dca4fdc058f98f41353be81f419ef Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 14:45:36 +0200 Subject: [PATCH 25/27] 100% coverage, fucking genious? --- src/PaletteProvider.js | 4 ++-- src/__tests__/PaletteProvider.test.js | 29 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/PaletteProvider.js b/src/PaletteProvider.js index bd2da39..511ddfc 100644 --- a/src/PaletteProvider.js +++ b/src/PaletteProvider.js @@ -117,9 +117,9 @@ export default class MaterialPaletteProvider (acc: *, profile: ColorProfile) => ({ ...acc, [profile]: { - ...((this.props.defaults + ...(this.props.defaults && this.props.defaults[profile] ? this.props.defaults[profile] - : defaultSwatches[profile]) || defaultSwatches[profile]), + : defaultSwatches[profile]), population: 0, }, }), diff --git a/src/__tests__/PaletteProvider.test.js b/src/__tests__/PaletteProvider.test.js index 6b490ed..d9d7679 100644 --- a/src/__tests__/PaletteProvider.test.js +++ b/src/__tests__/PaletteProvider.test.js @@ -1,5 +1,9 @@ /* eslint-disable import/first */ jest.mock('../index.js', () => ({ createMaterialPalette: jest.fn() })); +jest.mock('../utils/validateCreatePaletteArgs', () => ({ + __esModule: true, + validateDefaults: jest.fn(), +})); import React from 'react'; import { Text } from 'react-native'; @@ -225,6 +229,31 @@ describe('PaletteProvider', () => { ); }); + it('should merge palette with globals when props.defaults contains a wrong profile, for the types specified', done => { + createMaterialPalette.mockImplementation(() => + Promise.resolve({ + vibrant: defaultSwatches.vibrant, + })); + + function onFinish(palette) { + console.log(palette); + expect(palette).toEqual({ + vibrant: defaultSwatches.vibrant, + darkMuted: defaultSwatches.darkMuted, + }); + done(); + } + render( + , + ); + }); + it('should merge palette with both globals and local defaults, for the types specified', done => { createMaterialPalette.mockImplementation(() => Promise.resolve({ From 993bfe10fbfca37bc644e0e8816bf47fffa1e6b6 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 14:48:07 +0200 Subject: [PATCH 26/27] removing console.log --- src/__tests__/PaletteProvider.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/__tests__/PaletteProvider.test.js b/src/__tests__/PaletteProvider.test.js index d9d7679..c1c2999 100644 --- a/src/__tests__/PaletteProvider.test.js +++ b/src/__tests__/PaletteProvider.test.js @@ -236,7 +236,6 @@ describe('PaletteProvider', () => { })); function onFinish(palette) { - console.log(palette); expect(palette).toEqual({ vibrant: defaultSwatches.vibrant, darkMuted: defaultSwatches.darkMuted, From fef3d01939c94379d3329ceb4166ab4229c14244 Mon Sep 17 00:00:00 2001 From: Raul Gomez Acuna Date: Thu, 3 Aug 2017 15:03:51 +0200 Subject: [PATCH 27/27] simplifying mock --- src/utils/__tests__/validateCreatePalette.test.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/utils/__tests__/validateCreatePalette.test.js b/src/utils/__tests__/validateCreatePalette.test.js index 4d9a5f3..ed657ef 100644 --- a/src/utils/__tests__/validateCreatePalette.test.js +++ b/src/utils/__tests__/validateCreatePalette.test.js @@ -1,12 +1,5 @@ /* eslint-disable import/first */ -jest.mock('../validateCreatePaletteArgs', () => ({ - __esModule: true, - validateImage: jest.fn(), - validateOptionsKeys: jest.fn(), - validateType: jest.fn(), - validateMaximumColorCount: jest.fn(), - validateRegion: jest.fn(), -})); +jest.mock('../validateCreatePaletteArgs'); import validateCreatePalette from '../validateCreatePalette'; import { defaultOptions } from '../../constants/defaults';