diff --git a/.eslintignore b/.eslintignore index d79cdbb..81a97e2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ android/** ios/** __tests__/** **/tests/*** +web-build/** .eslintrc.js e2e/**/*.* metrics/* @@ -10,4 +11,6 @@ jest.setup.js babel.config.js reports report.json -**/tests/*.test.js \ No newline at end of file +growthbook.js +**/tests/*.test.js +webpack.config.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index a2da718..7d646b7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,6 @@ module.exports = { 'immutable', 'sonarjs', 'prettier', - 'redux-saga', 'react-native', 'react', 'react-hooks', @@ -95,8 +94,6 @@ module.exports = { 'react/require-extension': 0, 'react/self-closing-comp': 0, 'react/sort-comp': 0, - 'redux-saga/no-yield-in-race': 2, - 'redux-saga/yield-effects': 2, 'require-yield': 0, 'react/no-array-index-key': 0, 'react/jsx-curly-newline': 0, @@ -119,7 +116,7 @@ module.exports = { } ], 'no-shadow': 'error', - complexity: ['error', 2], + complexity: ['error', 4], 'no-empty': 'error', 'import/order': [ 'error', @@ -145,7 +142,8 @@ module.exports = { } ], 'fp/no-nil': 0, - 'fp/no-unused-expression': 0 + 'fp/no-unused-expression': 0, + 'fp/no-throw': 0 }, settings: { 'import/resolver': { diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9166e58..22ad456 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,9 @@ name: react-native-template on: push: - branches: [ master ] + branches: [master, dev] pull_request: - branches: [ master ] + branches: [master, dev] jobs: install-and-test: runs-on: ubuntu-latest @@ -13,7 +13,7 @@ jobs: run: yarn - name: Lint - run: npm run lint + run: yarn lint - name: Test and generate coverage report uses: artiomtr/jest-coverage-report-action@v2.2.9 diff --git a/.github/workflows/cd-prod-ios.yaml b/.github/workflows/cd-prod-ios.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/app/app.js b/app/app.js index 68c5387..f099239 100644 --- a/app/app.js +++ b/app/app.js @@ -1,24 +1,19 @@ import React from 'react'; +import { RecoilRoot } from 'recoil'; import { I18nextProvider } from 'react-i18next'; +import 'react-native-gesture-handler'; import LanguageProvider from '@atoms/LanguageProvider'; import RootScreen from '@scenes/RootScreen'; import i18n from '@app/i18n'; -import createStore from '@app/rootReducer'; -import { Provider } from 'react-redux'; -import { PersistGate } from 'redux-persist/lib/integration/react'; -import 'react-native-gesture-handler'; -const { store, persistor } = createStore(); const App = () => ( - - - - - - - - - + + + + + + + ); export default App; diff --git a/app/assets/images/wednesday-logo-new.png b/app/assets/images/wednesday-logo-new.png new file mode 100644 index 0000000..a7a9a9d Binary files /dev/null and b/app/assets/images/wednesday-logo-new.png differ diff --git a/app/assets/images/wednesday-logo-old.png b/app/assets/images/wednesday-logo-old.png new file mode 100755 index 0000000..db6d8a5 Binary files /dev/null and b/app/assets/images/wednesday-logo-old.png differ diff --git a/app/assets/images/wednesday-logo.png b/app/assets/images/wednesday-logo.png old mode 100755 new mode 100644 index db6d8a5..c7f3a03 Binary files a/app/assets/images/wednesday-logo.png and b/app/assets/images/wednesday-logo.png differ diff --git a/app/assets/images/wednesday-logo.svg b/app/assets/images/wednesday-logo.svg new file mode 100644 index 0000000..00ba80b --- /dev/null +++ b/app/assets/images/wednesday-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/components/atoms/Container/tests/__snapshots__/index.test.js.snap b/app/components/atoms/Container/tests/__snapshots__/index.test.js.snap index 1a1f520..0cf3656 100644 --- a/app/components/atoms/Container/tests/__snapshots__/index.test.js.snap +++ b/app/components/atoms/Container/tests/__snapshots__/index.test.js.snap @@ -3,12 +3,14 @@ exports[` should render and match the snapshot 1`] = ` diff --git a/app/components/atoms/LanguageProvider/actions.js b/app/components/atoms/LanguageProvider/actions.js deleted file mode 100644 index 8dfbef5..0000000 --- a/app/components/atoms/LanguageProvider/actions.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * - * LanguageProvider actions - * - */ - -import { CHANGE_LOCALE } from './constants'; -/** - * Changes the locale/language of the application. - * @param {string} languageLocale - The new locale/language to set. - * @returns {object} An action object with type 'CHANGE_LOCALE' and the new locale/language. - */ -export function changeLocale(languageLocale) { - return { - type: CHANGE_LOCALE, - locale: languageLocale - }; -} diff --git a/app/components/atoms/LanguageProvider/constants.js b/app/components/atoms/LanguageProvider/constants.js deleted file mode 100644 index f4d838d..0000000 --- a/app/components/atoms/LanguageProvider/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const CHANGE_LOCALE = 'changeLocale'; diff --git a/app/components/atoms/LanguageProvider/reducer.js b/app/components/atoms/LanguageProvider/reducer.js deleted file mode 100644 index b67a969..0000000 --- a/app/components/atoms/LanguageProvider/reducer.js +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable sonarjs/no-small-switch */ -/* - * - * LanguageProvider reducer - * - */ -import { createActions } from 'reduxsauce'; -import { fromJS } from 'immutable'; -import produce from 'immer'; -import { DEFAULT_LOCALE } from '@app/i18n'; - -export const { - Types: languageProviderTypes, - Creators: languageProviderActions -} = createActions({ - changeLocale: ['locale'] -}); - -export const initialState = fromJS({ - locale: DEFAULT_LOCALE -}); - -/* eslint-disable default-case, no-param-reassign */ -export const languageProviderReducer = (state = initialState, action) => - produce(state, (/* draft */) => { - switch (action.type) { - case languageProviderTypes.CHANGE_LOCALE: - return state.set('locale', action.locale); - default: - return state; - } - }); - -export default languageProviderReducer; diff --git a/app/components/atoms/LanguageProvider/selectors.js b/app/components/atoms/LanguageProvider/selectors.js deleted file mode 100644 index c5d4462..0000000 --- a/app/components/atoms/LanguageProvider/selectors.js +++ /dev/null @@ -1,16 +0,0 @@ -import { createSelector } from 'reselect'; -import { initialState } from './reducer'; - -/** - * Direct selector to the languageToggle state domain - */ -const selectLanguage = state => (state.language || initialState).toJS(); - -/** - * Select the language locale - */ - -const makeSelectLocale = () => - createSelector(selectLanguage, languageState => languageState.locale); - -export { selectLanguage, makeSelectLocale }; diff --git a/app/components/atoms/LanguageProvider/tests/action.test.js b/app/components/atoms/LanguageProvider/tests/action.test.js deleted file mode 100644 index 29e5b1c..0000000 --- a/app/components/atoms/LanguageProvider/tests/action.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { changeLocale } from '../actions'; -import { CHANGE_LOCALE } from '../constants'; - -describe('changeLocale action creator', () => { - it('should create an action to change the locale', () => { - const languageLocale = 'en'; - const expectedAction = { - type: CHANGE_LOCALE, - locale: languageLocale - }; - const action = changeLocale(languageLocale); - expect(action).toEqual(expectedAction); - }); -}); diff --git a/app/components/atoms/LanguageProvider/tests/index.test.js b/app/components/atoms/LanguageProvider/tests/index.test.js index a728fea..f103dcc 100644 --- a/app/components/atoms/LanguageProvider/tests/index.test.js +++ b/app/components/atoms/LanguageProvider/tests/index.test.js @@ -1,38 +1,30 @@ -// import React from 'react'; -// import { render } from '@testing-library/react-native'; -// import { Provider } from 'react-redux'; -// import T from '@atoms/T'; -// import createStore from 'app/rootReducer'; -// import { translationMessages } from 'app/i18n'; -// import { renderWithI18next } from '@utils/testUtils'; -// import { Text } from 'react-native'; -// import ConnectedLanguageProvider, { LanguageProvider } from '../index'; -// describe(' container tests', () => { -// it('should render its children', () => { -// const children = ( -//

-// Test -//

-// ); -// const container = renderWithI18next( -// -// {children} -// -// ); -// expect(container.firstChild).not.toBeNull(); -// }); -// }); -// const setupReduxStore = () => ({ reduxStore: createStore().store }); -// describe(' container tests', () => { -// it('should render the default language messages', () => { -// const { reduxStore } = setupReduxStore(); -// const { queryByText } = render( -// -// -// -// -// -// ); -// expect(queryByText('because')).not.toBeNull(); -// }); -// }); +import React from 'react'; +import { render } from '@testing-library/react-native'; +import T from '@atoms/T'; +import { renderWithI18next } from '@utils/testUtils'; +import { Text } from 'react-native'; +import ConnectedLanguageProvider, { LanguageProvider } from '../index'; +describe(' container tests', () => { + it('should render its children', () => { + const children = ( +

+ Test +

+ ); + const container = renderWithI18next( + {children} + ); + expect(container.firstChild).not.toBeNull(); + }); +}); + +describe(' container tests', () => { + it('should render the default language messages', () => { + const { queryByText } = render( + + + + ); + expect(queryByText('because')).not.toBeNull(); + }); +}); diff --git a/app/components/atoms/LanguageProvider/tests/reducer.test.js b/app/components/atoms/LanguageProvider/tests/reducer.test.js deleted file mode 100644 index f1288dc..0000000 --- a/app/components/atoms/LanguageProvider/tests/reducer.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - initialState, - languageProviderTypes, - languageProviderReducer -} from '../reducer'; -const setupMockedState = state => ({ mockedState: state }); -/* eslint-disable default-case, no-param-reassign */ -describe('Tests for LanguageProvider actions', () => { - it('returns the initial state', () => { - const { mockedState } = setupMockedState(initialState); - expect(languageProviderReducer(undefined, {})).toEqual(mockedState); - }); - - it('changes the locale', () => { - const locale = 'de'; - const { mockedState } = setupMockedState( - initialState.set('locale', locale) - ); - expect( - languageProviderReducer(undefined, { - type: languageProviderTypes.CHANGE_LOCALE, - locale - }) - ).toEqual(mockedState); - }); -}); diff --git a/app/components/atoms/LanguageProvider/tests/selectors.test.js b/app/components/atoms/LanguageProvider/tests/selectors.test.js deleted file mode 100644 index e4fb65d..0000000 --- a/app/components/atoms/LanguageProvider/tests/selectors.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fromJS } from 'immutable'; -import { selectLanguage } from '../selectors'; - -describe('Tests for LanguageProvider selectors', () => { - it('should select the global state', () => { - const globalState = {}; - const mockedState = { - language: fromJS(globalState) - }; - expect(selectLanguage(mockedState)).toEqual(globalState); - }); -}); diff --git a/app/components/molecules/CharacterWithQuote/tests/__snapshots__/index.test.js.snap b/app/components/molecules/CharacterWithQuote/tests/__snapshots__/index.test.js.snap index 1d2d373..e32d2e2 100644 --- a/app/components/molecules/CharacterWithQuote/tests/__snapshots__/index.test.js.snap +++ b/app/components/molecules/CharacterWithQuote/tests/__snapshots__/index.test.js.snap @@ -4,27 +4,29 @@ exports[` Should render and match the snapshot 1`] = ` [ wednesday_lover , because , @@ -36,27 +38,30 @@ exports[` Should render and match the snapshot 1`] = ` } } style={ - { - "height": 80, - "marginBottom": 0, - "marginLeft": "auto", - "marginRight": "auto", - "marginTop": 0, - "width": 80, - } + [ + { + "height": 80, + "marginBottom": 0, + "marginLeft": "auto", + "marginRight": "auto", + "marginTop": 0, + "width": 80, + }, + ] } testID="character-image" />, , ] `; diff --git a/app/components/molecules/LogoWithInstructions/tests/__snapshots__/index.test.js.snap b/app/components/molecules/LogoWithInstructions/tests/__snapshots__/index.test.js.snap index 5f9e96c..6dba70c 100644 --- a/app/components/molecules/LogoWithInstructions/tests/__snapshots__/index.test.js.snap +++ b/app/components/molecules/LogoWithInstructions/tests/__snapshots__/index.test.js.snap @@ -15,7 +15,7 @@ exports[` Should render and match the snapshot 1`] = ` resizeMode="contain" source={ { - "testUri": "../../../app/assets/images/wednesday-logo.png", + "testUri": "../../../app/assets/images/wednesday-logo-new.png", } } style={ @@ -29,15 +29,16 @@ exports[` Should render and match the snapshot 1`] = `
`; diff --git a/app/components/organisms/SimpsonsLoveWednesday/tests/__snapshots__/index.test.js.snap b/app/components/organisms/SimpsonsLoveWednesday/tests/__snapshots__/index.test.js.snap index 5395716..8870df6 100644 --- a/app/components/organisms/SimpsonsLoveWednesday/tests/__snapshots__/index.test.js.snap +++ b/app/components/organisms/SimpsonsLoveWednesday/tests/__snapshots__/index.test.js.snap @@ -16,7 +16,7 @@ exports[` Should render and match the snapshot 1`] = ` resizeMode="contain" source={ { - "testUri": "../../../app/assets/images/wednesday-logo.png", + "testUri": "../../../app/assets/images/wednesday-logo-new.png", } } style={ @@ -30,43 +30,50 @@ exports[` Should render and match the snapshot 1`] = ` , wednesday_lover because @@ -78,27 +85,30 @@ exports[` Should render and match the snapshot 1`] = ` } } style={ - { - "height": 80, - "marginBottom": 0, - "marginLeft": "auto", - "marginRight": "auto", - "marginTop": 0, - "width": 80, - } + [ + { + "height": 80, + "marginBottom": 0, + "marginLeft": "auto", + "marginRight": "auto", + "marginTop": 0, + "width": 80, + }, + ] } testID="character-image" /> , ] diff --git a/app/i18n.js b/app/i18n.js index b3a509d..5338e08 100644 --- a/app/i18n.js +++ b/app/i18n.js @@ -10,20 +10,10 @@ import { initReactI18next } from 'react-i18next'; * script `extract-intl`, and must use CommonJS module syntax * You CANNOT use import/export in this file. */ -const addLocaleData = require('react-intl').addLocaleData; //eslint-disable-line - -const enLocaleData = require('react-intl/locale-data/en'); const enTranslationMessages = require('./translations/en.json'); -addLocaleData(enLocaleData); -export const DEFAULT_LOCALE = 'en'; - -// prettier-ignore -export const appLocales = [ - 'en', -]; -const languageDetector = { +export const languageDetector = { type: 'languageDetector', async: true, detect: cb => cb('en'), @@ -48,22 +38,3 @@ i18n }); export default i18n; - -export const formatTranslationMessages = (locale, messages) => { - const defaultFormattedMessages = - locale !== DEFAULT_LOCALE - ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages) - : {}; - const flattenFormattedMessages = (formattedMessages, key) => { - const formattedMessage = - !messages[key] && locale !== DEFAULT_LOCALE - ? defaultFormattedMessages[key] - : messages[key]; - return Object.assign(formattedMessages, { [key]: formattedMessage }); - }; - return Object.keys(messages).reduce(flattenFormattedMessages, {}); -}; - -export const translationMessages = { - en: formatTranslationMessages('en', enTranslationMessages) -}; diff --git a/app/i18n.test.js b/app/i18n.test.js index 0f3c5c8..9d082be 100644 --- a/app/i18n.test.js +++ b/app/i18n.test.js @@ -1,51 +1,10 @@ -// import { formatTranslationMessages } from './i18n'; - -// jest.mock('app/translations/en.json', () => ({ -// message1: 'default message', -// message2: 'default message 2' -// })); - -// const esTranslationMessages = { -// message1: 'mensaje predeterminado', -// message2: '' -// }; - -// describe('Tests for formatTranslationMessages', () => { -// it('should build only defaults when DEFAULT_LOCALE', () => { -// const result = formatTranslationMessages('en', { a: 'a' }); - -// expect(result).toEqual({ a: 'a' }); -// }); - -// it('should combine default locale and current locale when not DEFAULT_LOCALE', () => { -// const result = formatTranslationMessages('', esTranslationMessages); - -// expect(result).toEqual({ -// message1: 'mensaje predeterminado', -// message2: 'default message 2' -// }); -// }); -// }); - import i18next from 'i18next'; import { initReactI18next } from 'react-i18next'; - -jest.mock('i18next', () => ({ - use: jest.fn().mockReturnThis(), - init: jest.fn().mockReturnThis() -})); - -jest.mock('react-i18next', () => ({ - initReactI18next: { - type: '3rdParty', - init: jest.fn() - } -})); +import { languageDetector } from './i18n'; describe('i18n configuration', () => { it('should configure i18next with the correct settings', () => { // Import the i18n configuration - require('./i18n'); // Verify that the language detector was used expect(i18next.use).toHaveBeenCalledWith( @@ -65,7 +24,7 @@ describe('i18n configuration', () => { expect(i18next.init).toHaveBeenCalledWith( expect.objectContaining({ fallbackLng: 'en', - debug: true, + debug: false, resources: { en: { translation: expect.any(Object) // This should match the contents of enTranslationMessages @@ -79,10 +38,6 @@ describe('i18n configuration', () => { }); it('should detect language as "en" using the language detector', () => { - const languageDetector = { - detect: jest.fn() - }; - // Call the detect function and ensure it was passed 'en' languageDetector.detect(language => { expect(language).toBe('en'); diff --git a/app/navigators/appNavigator.js b/app/navigators/appNavigator.js index 3ad9469..51fee5b 100644 --- a/app/navigators/appNavigator.js +++ b/app/navigators/appNavigator.js @@ -13,7 +13,10 @@ const Stack = createStackNavigator(); export default function AppNavigator() { return ( - + diff --git a/app/rootReducer.js b/app/rootReducer.js deleted file mode 100644 index 21cf2ff..0000000 --- a/app/rootReducer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { combineReducers } from 'redux'; -import { exampleContainerReducer as example } from '@scenes/ExampleScreen/reducer'; -import configureStore from '@app/utils/createStore'; -import rootSaga from '@app/rootSaga'; - -export default () => { - const rootReducer = combineReducers({ - example - }); - - return configureStore(rootReducer, rootSaga); -}; diff --git a/app/rootSaga.js b/app/rootSaga.js deleted file mode 100644 index a025ca9..0000000 --- a/app/rootSaga.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fork } from 'redux-saga/effects'; -import exampleSaga from '@scenes/ExampleScreen/saga'; -import startupSaga from '@scenes/RootScreen/saga'; -/** - * Root saga generator function that orchestrates other sagas. - * This function sets up and manages the execution of multiple sagas using fork effects. - * @returns {IterableIterator} An iterator for managing the execution of sagas. - */ -export default function* root() { - yield fork(exampleSaga); - yield fork(startupSaga); -} diff --git a/app/scenes/ExampleScreen/index.js b/app/scenes/ExampleScreen/index.js index 0102d4b..fc6f3c3 100644 --- a/app/scenes/ExampleScreen/index.js +++ b/app/scenes/ExampleScreen/index.js @@ -1,28 +1,20 @@ +import React, { useEffect } from 'react'; import { Button, Platform, View, ActivityIndicator } from 'react-native'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { PropTypes } from 'prop-types'; +import { + useRecoilState, + useSetRecoilState, + useRecoilValueLoadable +} from 'recoil'; import styled from 'styled-components/native'; -import { createStructuredSelector } from 'reselect'; -import { injectIntl } from 'react-intl'; -import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import AppContainer from '@atoms/Container'; import SimpsonsLoveWednesday from '@organisms/SimpsonsLoveWednesday'; +import If from '@app/components/atoms/If'; +import { conditionalOperatorFunction } from '@app/utils/common'; +import { LoadingStates } from '@app/utils/constants'; -import { - selectUser, - selectUserIsLoading, - selectUserErrorMessage -} from './selectors'; -import { exampleScreenActions } from './reducer'; - -/** - * This is an example of a container component. - * - * This screen displays a little help message and informations about a fake user. - * Feel free to remove it. - */ +import { userState, fetchUserSelector, fetchTriggerState } from './recoilState'; const Container = styled(AppContainer)` margin: 30px; @@ -38,56 +30,58 @@ const CustomButtonParentView = styled(View)` max-width: 80px; align-self: center; `; + const instructions = Platform.select({ ios: 'Press Cmd+R to reload,\nCmd+D or shake for dev menu.', android: 'Double tap R on your keyboard to reload,\nShake or press menu button for dev menu.' }); -const ExampleScreen = props => { +const ExampleScreen = () => { + const [user, setUser] = useRecoilState(userState); + const setFetchTrigger = useSetRecoilState(fetchTriggerState); + const userLoadable = useRecoilValueLoadable(fetchUserSelector); + const { t } = useTranslation(); const requestFetchUser = () => { - props.fetchUser(); + setFetchTrigger(prev => prev + 1); }; + useEffect(() => { requestFetchUser(); }, []); + + useEffect(() => { + if (userLoadable.state === LoadingStates.HAS_VALUE) { + setUser(userLoadable.contents); + } + }, [userLoadable?.contents?.character]); + return ( - {props.userIsLoading ? ( + + + + + + + } + > - ) : ( - - - -