From 25c192040adca2ab30f2329de952d5a3ebafd8ee Mon Sep 17 00:00:00 2001 From: OSPFNeighbour Date: Tue, 17 Jul 2018 10:57:37 +1000 Subject: [PATCH 1/3] Moved tag selector to a modal and added tests --- client/src/components/Modal/TagModal.js | 117 ++ .../components/Modal/__tests__/TagModal.js | 65 + .../__tests__/__snapshots__/TagModal.js.snap | 1550 +++++++++++++++++ client/src/components/Modal/index.js | 3 +- client/src/components/Modal/styles.js | 28 + client/src/screens/groups/NewGroup.js | 27 +- client/src/screens/groups/TagModal.js | 125 -- client/src/screens/groups/styles.js | 30 - 8 files changed, 1778 insertions(+), 167 deletions(-) create mode 100644 client/src/components/Modal/TagModal.js create mode 100644 client/src/components/Modal/__tests__/TagModal.js create mode 100644 client/src/components/Modal/__tests__/__snapshots__/TagModal.js.snap delete mode 100644 client/src/screens/groups/TagModal.js diff --git a/client/src/components/Modal/TagModal.js b/client/src/components/Modal/TagModal.js new file mode 100644 index 00000000..8498d29f --- /dev/null +++ b/client/src/components/Modal/TagModal.js @@ -0,0 +1,117 @@ +import React, { Component } from 'react'; +import CheckBox from 'react-native-check-box'; + +import PropTypes from 'prop-types'; +import { View, Modal, TouchableOpacity, TouchableWithoutFeedback, Text, ScrollView } from 'react-native'; +import { SearchBar } from 'react-native-elements'; + +import { Center } from '../Container'; + +import styles from './styles'; + +class TagModal extends Component { + isChecked = (option) => { + const index = this.props.selectedTags.indexOf(option.id); + return index !== -1; + }; + + renderOption(option, isLastItem) { + return ( + + + this.props.onSelect(option)} + isChecked={this.isChecked(option)} + /> + + + ); + } + + renderOptionList() { + const options = this.props.dataIn.map((item, index) => + this.renderOption(item, index === this.props.dataIn.length - 1)); + + if (options.length) { + return [options]; + } + return ( +
+ No matching tags found +
+ ); + } + + render() { + return ( + + + + + + + + {this.props.headerText} + + + + + this.props.onSearch(text)} + onClearText={null} + placeholder="Search" + /> + + + + + + {this.renderOptionList()} + + + + + + Save + + + + + + + + ); + } +} +TagModal.propTypes = { + dataIn: PropTypes.array, // eslint-disable-line react/forbid-prop-types + selectedTags: PropTypes.array, // eslint-disable-line react/forbid-prop-types + headerText: PropTypes.string.isRequired, + closeModal: PropTypes.func.isRequired, + onSearch: PropTypes.func.isRequired, + onSelect: PropTypes.func.isRequired, + isLoading: PropTypes.bool, + backModal: PropTypes.func.isRequired, + visible: PropTypes.bool.isRequired, +}; + +export default TagModal; diff --git a/client/src/components/Modal/__tests__/TagModal.js b/client/src/components/Modal/__tests__/TagModal.js new file mode 100644 index 00000000..af7c4393 --- /dev/null +++ b/client/src/components/Modal/__tests__/TagModal.js @@ -0,0 +1,65 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; + +import TagModal from '../TagModal'; + +const noop = () => {}; + +test('renders correctly', () => { + const normal = renderer.create( + , + ).toJSON(); + const selection = renderer.create( + , + ).toJSON(); + const loading = renderer.create( + , + ).toJSON(); + const empty = renderer.create( + , + ).toJSON(); + expect(normal).toMatchSnapshot(); + expect(selection).toMatchSnapshot(); + expect(loading).toMatchSnapshot(); + expect(empty).toMatchSnapshot(); +}); diff --git a/client/src/components/Modal/__tests__/__snapshots__/TagModal.js.snap b/client/src/components/Modal/__tests__/__snapshots__/TagModal.js.snap new file mode 100644 index 00000000..30a72c75 --- /dev/null +++ b/client/src/components/Modal/__tests__/__snapshots__/TagModal.js.snap @@ -0,0 +1,1550 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + + + + + + test + + + + + + + + +  + + + + + + + + + + + + + + + + item1 + + + + + + + + + + + + + + Save + + + + + + + +`; + +exports[`renders correctly 2`] = ` + + + + + + + + test + + + + + + + + +  + + + + + + + + + + + + + + + + item1 + + + + + + + + + + + + + + Save + + + + + + + +`; + +exports[`renders correctly 3`] = ` + + + + + + + + test + + + + + + + + +  + + + + + + + + + + + + + + + + item1 + + + + + + + + + + + + + + Save + + + + + + + +`; + +exports[`renders correctly 4`] = ` + + + + + + + + test + + + + + + + + +  + + + + + + + + + + + + No matching tags found + + + + + + + + + + + Save + + + + + + + +`; diff --git a/client/src/components/Modal/index.js b/client/src/components/Modal/index.js index b8365d8f..0b2b609e 100644 --- a/client/src/components/Modal/index.js +++ b/client/src/components/Modal/index.js @@ -3,6 +3,7 @@ import ListModal from './ListModal'; import NumberInputModal from './NumberInputModal'; import TextInputModal from './TextInputModal'; import MapModal from './MapModal'; +import TagModal from './TagModal'; import styles from './styles'; -export { Modal, ListModal, NumberInputModal, TextInputModal, MapModal, styles }; +export { Modal, ListModal, NumberInputModal, TextInputModal, MapModal, TagModal, styles }; diff --git a/client/src/components/Modal/styles.js b/client/src/components/Modal/styles.js index 723d6003..8ac770e9 100644 --- a/client/src/components/Modal/styles.js +++ b/client/src/components/Modal/styles.js @@ -31,6 +31,19 @@ const styles = StyleSheet.create({ backgroundColor: 'rgba(255,255,255,0.8)', }, + optionContainerTags: { + borderBottomLeftRadius: BORDER_RADIUS, + borderBottomRightRadius: BORDER_RADIUS, + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + justifyContent: 'center', + flexShrink: 1, + marginBottom: 8, + height: '50%', + padding: PADDING, + backgroundColor: 'rgba(255,255,255,0.9)', + }, + cancelContainer: { alignSelf: 'stretch', }, @@ -140,6 +153,21 @@ const styles = StyleSheet.create({ padding: PADDING, backgroundColor: 'rgba(255,255,255,0.8)', }, + searchContainer: { + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + borderTopLeftRadius: BORDER_RADIUS, + borderTopRightRadius: BORDER_RADIUS, + marginTop: 8, + alignSelf: 'stretch', + backgroundColor: 'rgba(255,255,255,0.9)', + padding: PADDING, + }, + searchbar: { + backgroundColor: 'transparent', + borderTopWidth: 0, + borderBottomWidth: 0, + }, map: { backgroundColor: '#f9f9f9', diff --git a/client/src/screens/groups/NewGroup.js b/client/src/screens/groups/NewGroup.js index ef25e042..76f64594 100644 --- a/client/src/screens/groups/NewGroup.js +++ b/client/src/screens/groups/NewGroup.js @@ -13,9 +13,8 @@ import { connect } from 'react-redux'; import { Container } from '../../components/Container'; import { Button } from '../../components/Button'; import { Progress } from '../../components/Progress'; - +import { TagModal } from '../../components/Modal'; import IconModal from './IconModal'; -import TagModal from './TagModal'; import groupIcons from '../../fixtures/icons'; import CURRENT_USER_QUERY from '../../graphql/current-user.query'; import { CREATE_GROUP_MUTATION } from '../../graphql/group.mutation'; @@ -70,17 +69,9 @@ class NewGroup extends Component { }); } -handleTagChange = (changedTag) => { - const newTags = [...this.state.tags]; - const index = newTags.findIndex(el => el.label === changedTag.label); - newTags[index] = { ...newTags[index], checked: !newTags[index].checked }; - this.setState({ tags: newTags }); -} - -handleTagBack = (tags) => { +handleTagBack = () => { this.setState({ tagsModal: false, - tags, }); } @@ -97,6 +88,17 @@ searchTagOnPress = (text) => { }); } +handleTagChange = (option) => { + const tmpSelectedTags = [...this.state.tags]; + const index = tmpSelectedTags.indexOf(option.id); + if (index !== -1) { + tmpSelectedTags.splice(index, 1); + } else { + tmpSelectedTags.push(option.id); + } + this.setState({ tags: tmpSelectedTags }); +} + applyTagSearchFilter = () => { this.props.refetch({ nameFilter: this.state.filterString }); } @@ -188,9 +190,12 @@ applyTagSearchFilter = () => { visible={this.state.tagsModal} closeModal={this.handleTagBack} onSearch={text => this.searchTagOnPress(text)} + onSelect={option => this.handleTagChange(option)} isLoading={loading} + headerText="Select Group Tags" backModal={tags => this.handleTagBack(tags)} dataIn={user && user.organisation.tags} + selectedTags={this.state.tags} /> ); diff --git a/client/src/screens/groups/TagModal.js b/client/src/screens/groups/TagModal.js deleted file mode 100644 index 228d2d9f..00000000 --- a/client/src/screens/groups/TagModal.js +++ /dev/null @@ -1,125 +0,0 @@ -import React, { Component } from 'react'; -import CheckBox from 'react-native-check-box'; - -import PropTypes from 'prop-types'; -import { View, Modal, TouchableOpacity, Text, ScrollView } from 'react-native'; -import { SearchBar } from 'react-native-elements'; - -import { Center } from '../../components/Container'; - -import styles from './styles'; - -class TagModal extends Component { - state = { - checked: [], - }; - - isChecked = (option) => { - const index = this.state.checked.indexOf(option.id); - return index !== -1; - }; - - Checked = (option) => { - const newChecked = [...this.state.checked]; - const index = newChecked.indexOf(option.id); - if (index !== -1) { - newChecked.splice(index, 1); - } else { - newChecked.push(option.id); - } - this.setState({ checked: newChecked }); - }; - - renderOption(option, isLastItem) { - return ( - - - this.Checked(option)} - isChecked={this.isChecked(option)} - /> - - - ); - } - - renderOptionList() { - const options = this.props.dataIn.map((item, index) => - this.renderOption(item, index === this.props.dataIn.length - 1)); - - if (options.length) { - return [options]; - } - return ( -
- No matching tags found -
- ); - } - - render() { - return ( - - - - - - - Select Group Tags - - - - - this.props.onSearch(text)} - onClearText={null} - placeholder="Search" - /> - - - - - - {this.renderOptionList()} - - - - this.props.backModal(this.state.checked)}> - - Save - - - - - - - ); - } -} -TagModal.propTypes = { - dataIn: PropTypes.array, // eslint-disable-line react/forbid-prop-types - closeModal: PropTypes.func.isRequired, - onSearch: PropTypes.func.isRequired, - isLoading: PropTypes.bool, - backModal: PropTypes.func.isRequired, - visible: PropTypes.bool.isRequired, -}; - -export default TagModal; diff --git a/client/src/screens/groups/styles.js b/client/src/screens/groups/styles.js index 63ad86d6..0ab98563 100644 --- a/client/src/screens/groups/styles.js +++ b/client/src/screens/groups/styles.js @@ -123,23 +123,6 @@ const styles = StyleSheet.create({ backgroundColor: 'rgba(0,0,0,0.7)', }, - searchContainer: { - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0, - borderTopLeftRadius: BORDER_RADIUS, - borderTopRightRadius: BORDER_RADIUS, - marginTop: 8, - alignSelf: 'stretch', - backgroundColor: 'rgba(255,255,255,0.9)', - padding: PADDING, - }, - - searchbar: { - backgroundColor: 'transparent', - borderTopWidth: 0, - borderBottomWidth: 0, - }, - optionContainer: { borderRadius: BORDER_RADIUS, justifyContent: 'center', @@ -151,19 +134,6 @@ const styles = StyleSheet.create({ backgroundColor: 'rgba(255,255,255,0.9)', }, - optionContainerTags: { - borderBottomLeftRadius: BORDER_RADIUS, - borderBottomRightRadius: BORDER_RADIUS, - borderTopLeftRadius: 0, - borderTopRightRadius: 0, - justifyContent: 'center', - flexShrink: 1, - marginBottom: 8, - height: '50%', - padding: PADDING, - backgroundColor: 'rgba(255,255,255,0.9)', - }, - cancelContainer: { alignSelf: 'stretch', }, From 43912951b53c2b68212785c055534582dadf09ba Mon Sep 17 00:00:00 2001 From: OSPFNeighbour Date: Tue, 24 Jul 2018 13:40:23 +1000 Subject: [PATCH 2/3] fixed closure(s) --- client/src/screens/groups/NewGroup.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/screens/groups/NewGroup.js b/client/src/screens/groups/NewGroup.js index 76f64594..9d6fbc59 100644 --- a/client/src/screens/groups/NewGroup.js +++ b/client/src/screens/groups/NewGroup.js @@ -189,11 +189,11 @@ applyTagSearchFilter = () => { this.searchTagOnPress(text)} - onSelect={option => this.handleTagChange(option)} + onSearch={this.searchTagOnPress} + onSelect={this.handleTagChange} isLoading={loading} headerText="Select Group Tags" - backModal={tags => this.handleTagBack(tags)} + backModal={this.handleTagBack} dataIn={user && user.organisation.tags} selectedTags={this.state.tags} /> From d0ae2a9755169553f0bf68d0b528a64d726bcab0 Mon Sep 17 00:00:00 2001 From: OSPFNeighbour Date: Tue, 24 Jul 2018 14:13:14 +1000 Subject: [PATCH 3/3] cleaned up another closure --- client/src/components/Modal/TagModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Modal/TagModal.js b/client/src/components/Modal/TagModal.js index 8498d29f..f84e510c 100644 --- a/client/src/components/Modal/TagModal.js +++ b/client/src/components/Modal/TagModal.js @@ -70,7 +70,7 @@ class TagModal extends Component { round showLoading={this.props.isLoading} containerStyle={styles.searchbar} - onChangeText={text => this.props.onSearch(text)} + onChangeText={this.props.onSearch} onClearText={null} placeholder="Search" />