From a49d075f2a6c65af7fda550700afbf948d5db787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Scarcella?= Date: Tue, 28 Mar 2017 14:24:23 -0300 Subject: [PATCH] Adding support for react-native styles as parameters for builders --- README.md | 18 ++++++- package.json | 6 +-- src/njsx.js | 8 +-- src/react-native.js | 76 ++++++++++++++++------------ src/rules.js | 13 +++++ test/njsx.test.js | 120 ++++++++++++++++++++++++++++++-------------- 6 files changed, 162 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 0e33459..aedb1ab 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Each argument is process by a set of configurable rules to decide what change it Notice that, since builders can be children too, most of the time there is no need to apply them with no args to create components. - - **Strings starting with a dot** will be interpreted as a *classes*. + - In *react* projects, **Strings starting with a dot** will be interpreted as a *classes*. ```jsx div('.thisIsAClass .thisIsAnotherClass')( @@ -94,6 +94,22 @@ Each argument is process by a set of configurable rules to decide what change it ) ``` + - In *react-native* projects, **StyleSheet entries** can be interpreted as *styles*. Just import `StyleSheet` from `njsx/react-native` instead of `react-native`. + + ```jsx + import {StyleSheet, View, Text} from 'njsx/react-native' + + // Same StyleSheet interface + StyleSheet.create({ + container: { /* ...your regular react-native styles... */ } + description: { /* ...your regular react-native styles... */ } + }) + + View(styles.container)( + Text(style.description)("These are styled!") + ) + ``` + - **Null** and **Undefined** arguments are ignored. diff --git a/package.json b/package.json index 0872bed..e7b8245 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "njsx", - "version": "2.0.5", + "version": "2.1.5", "description": "No-JSX: A customizable interface for creating React and React-Native components without JSX syntax.", "repository": "uqbar-project/njsx", "scripts": { "test": "mocha --compilers js:babel-register", - "package": "rm -rf lib && babel --presets react-native src -d lib && cp -t ./lib/ ./package.json ./README.md && cd lib && npm pack && cd ..", - "release": "cd lib && npm publish ./ && cd .." + "package": "npm test && rm -rf lib && babel --presets react-native src -d lib && cp -t ./lib/ ./package.json ./README.md && cd lib && npm pack && cd ..", + "release": "npm run package && cd lib && npm publish ./ && cd .." }, "dependencies": { "react": "^15.4.2" diff --git a/src/njsx.js b/src/njsx.js index d49b2c0..657e701 100644 --- a/src/njsx.js +++ b/src/njsx.js @@ -16,9 +16,11 @@ const asDynamic = (component) => { export default function njsx(type, props={}, children=[]) { const component = (...args) => { - const {props: finalProps, children: finalChildren} = flatten(args).reduce((previous, arg) => - njsx.rules.find(rule => rule.appliesTo(arg)).apply(arg, previous) - , {props,children}) + const {props: finalProps, children: finalChildren} = flatten(args).reduce((previous, arg) => { + const rule = njsx.rules.find(rule => rule.appliesTo(arg)) + if(!rule) {throw new TypeError(`Unsupported NJSX argument: ${arg}`)} + return rule.apply(arg, previous) + }, {props,children}) return args.length === 0 ? createElement(type, finalProps, ...finalChildren) diff --git a/src/react-native.js b/src/react-native.js index 7c03546..d4f1423 100644 --- a/src/react-native.js +++ b/src/react-native.js @@ -1,11 +1,13 @@ import ReactNative from 'react-native' import njsx from './njsx' import RULES from './rules' +const {entries, assign} = Object export const DEFAULT_REACT_NATIVE_RULES = [ RULES.STRING_AS_CHILD, RULES.NUMBER_AS_CHILD, RULES.BOOLEAN_AS_CHILD, + RULES.STYLE_AS_STYLE, RULES.NJSX_COMPONENT_AS_CHILD, RULES.REACT_COMPONENT_AS_CHILD, RULES.HASH_AS_ATRIBUTES, @@ -15,36 +17,44 @@ export const DEFAULT_REACT_NATIVE_RULES = [ njsx.rules = njsx.rules || DEFAULT_REACT_NATIVE_RULES -export const ActivityIndicator = njsx(ReactNative.ActivityIndicator) -export const Button = njsx(ReactNative.Button) -export const DatePickerIOS = njsx(ReactNative.DatePickerIOS) -export const DrawerLayoutAndroid = njsx(ReactNative.DrawerLayoutAndroid) -export const Image = njsx(ReactNative.Image) -export const KeyboardAvoidingView = njsx(ReactNative.KeyboardAvoidingView) -export const ListView = njsx(ReactNative.ListView) -export const MapView = (...args) => njsx(ReactNative.MapView)(args) -export const Modal = njsx(ReactNative.Modal) -export const Navigator = njsx(ReactNative.Navigator) -export const NavigatorIOS = njsx(ReactNative.NavigatorIOS) -export const Picker = njsx(ReactNative.Picker) -export const PickerIOS = njsx(ReactNative.PickerIOS) -export const ProgressBarAndroid = njsx(ReactNative.ProgressBarAndroid) -export const ProgressViewIOS = njsx(ReactNative.ProgressViewIOS) -export const RefreshControl = njsx(ReactNative.RefreshControl) -export const ScrollView = njsx(ReactNative.ScrollView) -export const SegmentedControlIOS = njsx(ReactNative.SegmentedControlIOS) -export const Slider = njsx(ReactNative.Slider) -export const SnapshotViewIOS = njsx(ReactNative.SnapshotViewIOS) -export const StatusBar = njsx(ReactNative.StatusBar) -export const Switch = njsx(ReactNative.Switch) -export const TabBarIOS = njsx(ReactNative.TabBarIOS) -export const TabBarIOSItem = njsx(ReactNative.TabBarIOS.Item) -export const Text = njsx(ReactNative.Text) -export const TextInput = njsx(ReactNative.TextInput) -export const ToolbarAndroid = njsx(ReactNative.ToolbarAndroid) -export const TouchableHighlight = njsx(ReactNative.TouchableHighlight) -export const TouchableNativeFeedback = njsx(ReactNative.TouchableNativeFeedback) -export const TouchableOpacity = njsx(ReactNative.TouchableOpacity) -export const TouchableWithoutFeedback = njsx(ReactNative.TouchableWithoutFeedback) -export const View = njsx(ReactNative.View) -export const ViewPagerAndroid = njsx(ReactNative.ViewPagerAndroid) \ No newline at end of file +export const StyleSheet = { + create(styleDefinition) { + return entries(ReactNative.StyleSheet.create(styleDefinition)).reduce((acum, [key, value]) => + assign(acum, {[key]: {styleId: value}} ) + , {}) + } +} + +export const ActivityIndicator = (...args) => njsx(ReactNative.ActivityIndicator)(...args) +export const Button = (...args) => njsx(ReactNative.Button)(...args) +export const DatePickerIOS = (...args) => njsx(ReactNative.DatePickerIOS)(...args) +export const DrawerLayoutAndroid = (...args) => njsx(ReactNative.DrawerLayoutAndroid)(...args) +export const Image = (...args) => njsx(ReactNative.Image)(...args) +export const KeyboardAvoidingView = (...args) => njsx(ReactNative.KeyboardAvoidingView)(...args) +export const ListView = (...args) => njsx(ReactNative.ListView)(...args) +export const MapView = (...args) => njsx(ReactNative.MapView)(...args) +export const Modal = (...args) => njsx(ReactNative.Modal)(...args) +export const Navigator = (...args) => njsx(ReactNative.Navigator)(...args) +export const NavigatorIOS = (...args) => njsx(ReactNative.NavigatorIOS)(...args) +export const Picker = (...args) => njsx(ReactNative.Picker)(...args) +export const PickerIOS = (...args) => njsx(ReactNative.PickerIOS)(...args) +export const ProgressBarAndroid = (...args) => njsx(ReactNative.ProgressBarAndroid)(...args) +export const ProgressViewIOS = (...args) => njsx(ReactNative.ProgressViewIOS)(...args) +export const RefreshControl = (...args) => njsx(ReactNative.RefreshControl)(...args) +export const ScrollView = (...args) => njsx(ReactNative.ScrollView)(...args) +export const SegmentedControlIOS = (...args) => njsx(ReactNative.SegmentedControlIOS)(...args) +export const Slider = (...args) => njsx(ReactNative.Slider)(...args) +export const SnapshotViewIOS = (...args) => njsx(ReactNative.SnapshotViewIOS)(...args) +export const StatusBar = (...args) => njsx(ReactNative.StatusBar)(...args) +export const Switch = (...args) => njsx(ReactNative.Switch)(...args) +export const TabBarIOS = (...args) => njsx(ReactNative.TabBarIOS)(...args) +export const TabBarIOSItem = (...args) => njsx(ReactNative.TabBarIOS.Item)(...args) +export const Text = (...args) => njsx(ReactNative.Text)(...args) +export const TextInput = (...args) => njsx(ReactNative.TextInput)(...args) +export const ToolbarAndroid = (...args) => njsx(ReactNative.ToolbarAndroid)(...args) +export const TouchableHighlight = (...args) => njsx(ReactNative.TouchableHighlight)(...args) +export const TouchableNativeFeedback = (...args) => njsx(ReactNative.TouchableNativeFeedback)(...args) +export const TouchableOpacity = (...args) => njsx(ReactNative.TouchableOpacity)(...args) +export const TouchableWithoutFeedback = (...args) => njsx(ReactNative.TouchableWithoutFeedback)(...args) +export const View = (...args) => njsx(ReactNative.View)(...args) +export const ViewPagerAndroid = (...args) => njsx(ReactNative.ViewPagerAndroid)(...args) \ No newline at end of file diff --git a/src/rules.js b/src/rules.js index 05525d4..ca9360c 100644 --- a/src/rules.js +++ b/src/rules.js @@ -1,3 +1,5 @@ +const {isArray} = Array + export default { HASH_AS_ATRIBUTES: { @@ -9,6 +11,17 @@ export default { appliesTo(arg) { return typeof arg === 'string' && arg.trim().startsWith('.') }, apply(arg, {props: {className = '', otherProps}, children}) { return {props: {...otherProps, className: [...className.split(' '), ...arg.split('.')].map(c=> c.trim()).filter(String).join(' ')} , children } } }, + + STYLE_AS_STYLE: { + appliesTo(arg) { return typeof arg === 'object' && arg.styleId }, + apply(arg, {props, children}) { + let style = null + if(!props.style) style = arg.styleId + else if(isArray(props.style)) style = [...props.style, arg.styleId] + else style = [props.style, arg.styleId] + return { props:{...props, style}, children } + } + }, STRING_AS_CHILD: { appliesTo(arg) { return typeof arg === 'string' }, diff --git a/test/njsx.test.js b/test/njsx.test.js index 7418376..db336e7 100644 --- a/test/njsx.test.js +++ b/test/njsx.test.js @@ -1,134 +1,176 @@ import { expect, assert } from 'chai' +import mock from 'mock-require' import React, {Component as ReactComponent} from 'react' import njsx from '../src/njsx.js' import Rules from '../src/rules.js' -const {defineProperty} = Object +const {keys, assign, defineProperty} = Object + +mock('react-native', { + StyleSheet: { + create: (styleDefinition) => keys(styleDefinition).reduce((acum, key) => assign(acum, {[key]: 1}), {}) + } +}) +const {StyleSheet} = require("../src/react-native.js") describe('NJSX', () => { - describe('elements', () => { + describe('components', () => { it('should be creatable from a type name', () => { - const element = njsx('foo')() + const component = njsx('foo')() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be creatable from a React component', () => { class Component extends ReactComponent { render() { return } } - const element = njsx(Component)() + const component = njsx(Component)() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be creatable from a React functional component', () => { const FunctionalComponent = () => createReactElement('foo') - const element = njsx(FunctionalComponent)() + const component = njsx(FunctionalComponent)() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be refinable by passing attributes as a hash', () => { njsx.rules = [Rules.HASH_AS_ATRIBUTES] - const element = njsx('foo')({bar: 'baz'})() + const component = njsx('foo')({bar: 'baz'})() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be refinable by passing a string representing a class name', () => { njsx.rules = [Rules.STRING_AS_CLASS] - const element = njsx('foo')('.bar.baz')('.qux')() + const component = njsx('foo')('.bar.baz')('.qux')() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be refinable by passing a string representing content', () => { njsx.rules = [Rules.STRING_AS_CHILD] - const element = njsx('foo')('bar')() + const component = njsx('foo')('bar')() - expect(element).to.deep.equal(bar) + expect(component).to.deep.equal(bar) }) it('should be refinable by passing a number representing content', () => { njsx.rules = [Rules.NUMBER_AS_CHILD] - const element = njsx('foo')(5)() + const component = njsx('foo')(5)() - expect(element).to.deep.equal(5) + expect(component).to.deep.equal(5) }) it('should be refinable by passing a boolean representing content', () => { njsx.rules = [Rules.BOOLEAN_AS_CHILD] - const element = njsx('foo')(false)() + const component = njsx('foo')(false)() - expect(element).to.deep.equal(false) + expect(component).to.deep.equal(false) }) - it('should be refinable by passing other React elements as children', () => { + it('should be refinable by passing other React components as children', () => { njsx.rules = [Rules.REACT_COMPONENT_AS_CHILD] - const element = njsx('foo')(,)() + const component = njsx('foo')(,)() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) - it('should be refinable by passing other NJSX elements as children', () => { + it('should be refinable by passing other NJSX components as children', () => { njsx.rules = [Rules.NJSX_COMPONENT_AS_CHILD] - const element = njsx('foo')(njsx('bar'), njsx('baz'))() + const component = njsx('foo')(njsx('bar'), njsx('baz'))() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be refinable by passing an array of children', () => { njsx.rules = [Rules.NJSX_COMPONENT_AS_CHILD] - const element = njsx('foo')([njsx('bar'), njsx('baz')])() + const component = njsx('foo')([njsx('bar'), njsx('baz')])() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should ignore null arguments', () => { njsx.rules = [Rules.IGNORE_NULL] - const element = njsx('foo')(null, [null])() + const component = njsx('foo')(null, [null])() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should ignore undefined arguments', () => { njsx.rules = [Rules.IGNORE_UNDEFINED] - const element = njsx('foo')(undefined,[undefined])() + const component = njsx('foo')(undefined,[undefined])() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should not be refinable by invalid arguments', () => { - const element = njsx('foo') + const component = njsx('foo') - expect(() => element((invalid) => 'argument')).to.throw(TypeError) + expect(() => component("unsupported argument")).to.throw(TypeError) }) it('should be refinable by dynamic messages if a handler is defined', () => { njsx.dynamicSelectorHandler = Rules.STRING_AS_CLASS.apply - const element = njsx('foo').bar.baz.qux() + const component = njsx('foo').bar.baz.qux() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should be refinable by property key accessing if a handler is defined', () => { njsx.dynamicSelectorHandler = Rules.STRING_AS_CLASS.apply - const element = njsx('foo')['.bar']['baz qux']() + const component = njsx('foo')['.bar']['baz qux']() - expect(element).to.deep.equal() + expect(component).to.deep.equal() }) it('should not be refinable by dynamic messages if a handler is not defined', () => { njsx.dynamicSelectorHandler = undefined - const element = njsx('foo').bar + const component = njsx('foo').bar - expect(element).to.deep.equal(undefined) + expect(component).to.deep.equal(undefined) }) it('should not be refinable by dynamic messages after the component is built', () => { njsx.dynamicSelectorHandler = assert.fail - const element = njsx('foo')().bar + const component = njsx('foo')().bar }) + + describe('should be refinable by njsx styles when', () => { + const styleSheet = StyleSheet.create({style: {bar: "baz"} }) + + it('no previous style is defined', () => { + njsx.rules = [Rules.STYLE_AS_STYLE] + const component = njsx('foo')(styleSheet.style)() + + expect(component).to.deep.equal() + }) + + it('style is already defined as array', () => { + njsx.rules = [Rules.STYLE_AS_STYLE, Rules.HASH_AS_ATRIBUTES] + const component = njsx('foo')({style: [5]})(styleSheet.style)() + + expect(component).to.deep.equal() + }) + + it('style is already defined as object', () => { + njsx.rules = [Rules.STYLE_AS_STYLE, Rules.HASH_AS_ATRIBUTES] + const component = njsx('foo')({style: {bar: "baz"}})(styleSheet.style)() + + expect(component).to.deep.equal() + }) + + it('style is already defined as id', () => { + njsx.rules = [Rules.STYLE_AS_STYLE, Rules.HASH_AS_ATRIBUTES] + const component = njsx('foo')({style: 5})(styleSheet.style)() + + expect(component).to.deep.equal() + }) + + }) + }) }) \ No newline at end of file