Skip to content

Commit

Permalink
Adding support for react-native styles as parameters for builders
Browse files Browse the repository at this point in the history
  • Loading branch information
nscarcella committed Mar 28, 2017
1 parent 18095bd commit a49d075
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 79 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,30 @@ 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')(
'Some content'
)
```

- 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.


Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
8 changes: 5 additions & 3 deletions src/njsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
76 changes: 43 additions & 33 deletions src/react-native.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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)
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)
13 changes: 13 additions & 0 deletions src/rules.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const {isArray} = Array

export default {

HASH_AS_ATRIBUTES: {
Expand All @@ -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' },
Expand Down
120 changes: 81 additions & 39 deletions test/njsx.test.js
Original file line number Diff line number Diff line change
@@ -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(<foo/>)
expect(component).to.deep.equal(<foo/>)
})

it('should be creatable from a React component', () => {
class Component extends ReactComponent { render() { return <foo/> } }
const element = njsx(Component)()
const component = njsx(Component)()

expect(element).to.deep.equal(<Component/>)
expect(component).to.deep.equal(<Component/>)
})

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(<FunctionalComponent/>)
expect(component).to.deep.equal(<FunctionalComponent/>)
})

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(<foo bar='baz'/>)
expect(component).to.deep.equal(<foo bar='baz'/>)
})

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(<foo className="bar baz qux"/>)
expect(component).to.deep.equal(<foo className="bar baz qux"/>)
})

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(<foo>bar</foo>)
expect(component).to.deep.equal(<foo>bar</foo>)
})

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(<foo>5</foo>)
expect(component).to.deep.equal(<foo>5</foo>)
})

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(<foo>false</foo>)
expect(component).to.deep.equal(<foo>false</foo>)
})

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')(<bar/>,<baz/>)()
const component = njsx('foo')(<bar/>,<baz/>)()

expect(element).to.deep.equal(<foo><bar/><baz/></foo>)
expect(component).to.deep.equal(<foo><bar/><baz/></foo>)
})

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(<foo><bar/><baz/></foo>)
expect(component).to.deep.equal(<foo><bar/><baz/></foo>)
})

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(<foo><bar/><baz/></foo>)
expect(component).to.deep.equal(<foo><bar/><baz/></foo>)
})

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(<foo/>)
expect(component).to.deep.equal(<foo/>)
})

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(<foo/>)
expect(component).to.deep.equal(<foo/>)
})

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(<foo className="bar baz qux"/>)
expect(component).to.deep.equal(<foo className="bar baz qux"/>)
})

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(<foo className="bar baz qux"/>)
expect(component).to.deep.equal(<foo className="bar baz qux"/>)
})

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(<foo style={1}/>)
})

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(<foo style={[5,1]}/>)
})

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(<foo style={[{bar: "baz"}, 1]}/>)
})

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(<foo style={[5,1]}/>)
})

})

})

})

0 comments on commit a49d075

Please sign in to comment.