Running js code after all dom rendered in Cycle.js - zurb-foundation

I want to use Foundation's reveal plugin to display a nice modal in a Cycle.js app.
After playing with Webpack and it's configuration for hours, I figured how to propertly import foundation's js into my (es6) app.
However, since I'm dealing with virtual dom, the actual html does not exist when foundation's js is initialized.
Here's my component's code:
import Rx from 'rx';
import {div} from '#cycle/dom';
import $ from 'jquery';
import {foundation} from 'foundation-sites/js/foundation.core';
import 'foundation-sites/js/foundation.util.keyboard';
import 'foundation-sites/js/foundation.util.box';
import 'foundation-sites/js/foundation.util.triggers';
import 'foundation-sites/js/foundation.util.mediaQuery';
import 'foundation-sites/js/foundation.util.motion';
import 'foundation-sites/js/foundation.reveal';
require('./styles/configuration-modal.scss');
function ConfigurationModal(sources) {
const values$ = sources.props$;
const vtree$ = Rx.Observable.just(1).map(() => {
return div(
'#config-modal.reveal',
{attributes: {
'data-reveal': ''
}},
['hello']
);
});
return {
DOM: vtree$,
values$
};
}
$(function() {
$.fn.foundation = foundation;
$(document).foundation();
});
export default ConfigurationModal;
My main.js:
import Rx from 'rx';
import Cycle from '#cycle/core';
import {makeDOMDriver, div} from '#cycle/dom';
import ConfigurationModal from './components/ConfigurationModal/index';
require('./styles/index.scss');
function main(sources) {
const props$ = Rx.Observable.of({
pomodoroDuration: 25 * 60,
shortBreakDuration: 5 * 60,
longBreakDuration: 15 * 60
});
const configurationModal = ConfigurationModal({
DOM: sources.DOM,
props$
});
return {
DOM: configurationModal.DOM,
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#app')
});
Now, if I run $(document).foundation() in the console once the app is initialized, things work as expected. But in the app code, since the dom is virtual and the actual html has not yet been injected, nothing appears.
How could I run js code after the app was fully initialized and the dom actually populated?

In Cycle, there's a useful feature of the DOM driver that allows you to subscribe to updates for elements.
In this case, you can run code after the app starts up like this:
DOM
.select(':root')
.element()
.take(1)
.subscribe((element) => $(document).foundation())
You could add that to your main, and that should solve your problem.
However, in Cycle it's idomatic to encapsulate all of your subscribes in drivers, as they're usually side effects. In this case, you can make a driver to start Foundation, like so:
function startFoundation () {
$(document).foundation();
}
function foundationDriver(sink$) {
return sink$.take(1).subscribe(startFoundation);
}
And in your main.js:
function main(sources) {
// ...
const startup$ = sources.DOM.select(':root').element().take(1);
return {
DOM: configurationModal.DOM,
FoundationStartup: startup$
};
}
Cycle.run(main, {
DOM: makeDOMDriver('#app'),
FoundationStartup: foundationDriver
});

Related

How to use getServerSideProps for every pages in next.js?

I have set a cookie with nookies which store the values of all the products selected by user.
I want to fetch the cookie in server side using getServerSideProps and pass the value as props. I have to display the value of cookie on all pages.
When I tried getServerSideProps in _app.js. It did not worked and it did not even run the code.
Is there any way to do it?
As of now, there isn't a built-in way to do it, so I've resorted to doing the following.
First, I created a file that holds the getServerSideProps function I want to run on every page:
// lib/serverProps.js
export default async function getServerSideProps(ctx) {
// do something
return {
// data
};
}
Then in every page (yes, every, I can't find a workaround; it might even be helpful if you don't need the code to execute on server pages), do:
import getServerSideProps from "../lib/serverProps";
// other stuff...
export { getServerSideProps };
or
// other stuff...
export { default as getServerSideProps } from "../lib/serverProps";
If you want to add other code to run inside getServerSideProps for a specific page, you could do something along the lines...
import serverProps from "../lib/serverProps";
// other stuff...
export async function getServerSideProps(ctx) {
// do custom page stuff...
return {
...await serverProps(ctx),
...{
// pretend this is what you put inside
// the return block regularly, e.g.
props: { junk: 347 }
}
};
}
getServerSideProps does not work in _app.js. see docs.
you could use the older getInitialProps in your custom app component but then the automatic static optimisation is disabled, which is something Next.js bets on heavily.
it might be worth digging into your cookie use case and figure out if you really need to read it on the server side.
For those wanting to share state received from a page's getServerSideProps function to global components in pages/_app.tsx, I've pieced this solution together.
Create a shared getServerSideProps function to include on all pages
Create a shared useSetUserStorage custom hook to include on all pages
Listen for localStorage changes with custom event listener in global component (e.g. GlobalNav)
It's a work around, but is working for me so far (note that it includes some specifics to my use of getServerSideProps function).
It's a fair amount of code but hopefully this helps someone:
// src/pages/_app.tsx
import type { AppProps } from "next/app";
import GlobalNav from "../components/GlobalNav";
function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<>
<GlobalNav /> // <— your global component
<Component {...pageProps} />
</>
);
}
export default MyApp;
// src/utils/getServerSideProps.ts
import { ppinit, ppsession, sess } from "../authMiddleware";
import nc from "next-connect";
import { NextApiRequest, NextApiResponse } from "next";
import { User } from "../types/types";
interface ExtendedReq extends NextApiRequest {
user: User;
}
interface ServerProps {
req: ExtendedReq;
res: NextApiResponse;
}
interface ServerPropsReturn {
user?: User;
}
//
// Here we use middleware to augment the `req` with the user from passport.js
// to pass to the page
// src: https://github.com/hoangvvo/next-connect/tree/21c9c73fe3746e66033fd51e2aa01d479e267ad6#runreq-res
//
const getServerSideProps = async ({ req, res }: ServerProps) => {
// ADD YOUR CUSTOM `getServerSideProps` code here
const middleware = nc()
.use(sess, ppinit, ppsession)
.get((req: Express.Request, res: NextApiResponse, next) => {
next();
});
try {
await middleware.run(req, res);
} catch (e) {
// handle the error
}
const props: ServerPropsReturn = {};
if (req.user) props.user = req.user;
return { props };
};
export interface Props {
user?: User;
}
export default getServerSideProps;
// src/hooks.ts
import { useEffect } from "react";
import { User } from "./types/types";
export const useSetUserStorage = (user?: User) => {
useEffect(() => {
if (user) {
localStorage.setItem("user", JSON.stringify(user));
} else {
localStorage.removeItem("user");
}
// whether setting or removing the user, dispatch event so that `GlobalNav`
// component (which is above the page implementing this hook in the
// component hierarchy) can be updated to display the user status. we
// can't use `window.addEventListener('storage', handler)` as this only
// works for listening for events from other pages
document.dispatchEvent(new Event("localStorageUserUpdated"));
});
return null;
};
// src/pages/index.tsx (or any page)
import { useSetUserStorage } from "../hooks";
import { Props } from "../utils/getServerSideProps";
export { default as getServerSideProps } from "../utils/getServerSideProps";
export default function Home({ user }: Props) {
useSetUserStorage(user);
return (
<>
<h1>Welcome to my app {user?.username}</h1>
</>
);
}
// src/components/GlobalNav.ts (or another global component)
import { useEffect, useState, MouseEvent } from "react";
import { User } from "../types/types";
const GlobalNav = () => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const handleUserLocalStorage = () => {
const userString = localStorage.getItem("user");
try {
if (userString) {
setUser(JSON.parse(userString));
} else {
setUser(null);
}
} catch (e) {
// handle parse error
}
};
handleUserLocalStorage();
// this component (`GlobalNav`) lives at the application level, above the
// pages, but the pages receive the user object from `getServerSideProps`,
// so this listener listens for when a page tells us the user object has
// changed so we can update the `user` state here.
document.addEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
false,
);
return () => {
// remove listener if component unmounts
document.removeEventListener(
"localStorageUserUpdated",
handleUserLocalStorage,
);
};
}, []);
return (
<div>
{user?.username}
</div>
);
};
export default GlobalNav;
I used a slightly different technique. Every page, in my case, has its own getServerSideProps and I was looking for a more functional approach. Also I'm using GraphQL, but the idea is the same no matter which data fetching API you choose. A regular getServerSideProps would look like this -
export const getServerSideProps: GetServerSideProps = async (context) => {
const { slug } = context.query
const { data: profile } = await client.query({ query: GetProfileDocument, variables: { slug } })
return {
props: {
...(await getSelf(context)),
profile: profile?.GetProfile[0],
},
}
}
In the props you can see the await statement, which is called in all pages. And in the few cases I don't need it, it's gone. This is what getSelf looks like -
const getSelf = async (context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) => {
const session = await getSession(context)
let self = null
if (session) {
const { data } = await client.query({
query: GetProfileDocument,
variables: { secret: session?.secretSauce as string },
})
self = data.GetProfile[0]
}
return { self, sessionData: session }
}
Hope it helped.

What is the point of unit testing redux-saga watchers?

In order to get 100% coverage of my Saga files I'm looking into how to test watchers.
I've been googling around, there are several answers as to HOW to test watchers. That is, saga's that do a takeEvery or takeLatest.
However, all methods of testing seem to basically copy the implementation. So what's the point of writing a test if it's the same?
Example:
// saga.js
import { delay } from 'redux-saga'
import { takeEvery, call, put } from 'redux-saga/effects'
import { FETCH_RESULTS, FETCH_COMPLETE } from './actions'
import mockResults from './tests/results.mock'
export function* fetchResults () {
yield call(delay, 1000)
yield put({ type: FETCH_COMPLETE, mockResults })
}
export function* watchFetchResults () {
yield takeEvery(FETCH_RESULTS, fetchResults)
}
Test method 1:
import { takeEvery } from 'redux-saga/effects'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'
describe('watchFetchResults()', () => {
const gen = watchFetchResults()
// exactly the same as implementation
const expected = takeEvery(FETCH_RESULTS, fetchResults)
const actual = gen.next().value
it('Should fire on FETCH_RESULTS', () => {
expect(actual).toEqual(expected)
})
})
Test method 2: with a helper, like Redux Saga Test Plan
It's a different way of writing, but again we do basically the same as the implementation.
import testSaga from 'redux-saga-test-plan'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'
it('fire on FETCH_RESULTS', () => {
testSaga(watchFetchResults)
.next()
.takeEvery(FETCH_RESULTS, fetchResults)
.finish()
.isDone()
})
Instead I'd like to simply know if watchFestchResults takes every FETCH_RESULTS. Or even only if it fires takeEvery(). No matter how it follows up.
Or is this really the way to do it?
It sounds like the point of testing them is to achieve 100% test coverage.
There are some things that you can unit test, but it is questionable if you should.
It seems to me that this situation might be a better candidate for an 'integration' test. Something that does not test simply a single method, but how several methods work together as a whole. Perhaps you could call an action that fires a reducer that uses your saga, then check the store for the resulting change? This would be far more meaningful than testing the saga alone.
I agree with John Meyer's answer that this is better suited for the integration test than for the unit test. This issue is the most popular in GitHub based on up votes. I would recommend reading it.
One of the suggestions is to use redux-saga-tester package created by opener of the issue. It helps to create initial state, start saga helpers (takeEvery, takeLatest), dispatch actions that saga is listening on, observe the state, retrieve a history of actions and listen for specific actions to occur.
I am using it with axios-mock-adapter, but there are several examples in the codebase using nock.
Saga
import { takeLatest, call, put } from 'redux-saga/effects';
import { actions, types } from 'modules/review/reducer';
import * as api from 'api';
export function* requestReviews({ locale }) {
const uri = `/reviews?filter[where][locale]=${locale}`;
const response = yield call(api.get, uri);
yield put(actions.receiveReviews(locale, response.data[0].services));
}
// Saga Helper
export default function* watchRequestReviews() {
yield takeLatest(types.REVIEWS_REQUEST, requestReviews);
}
Test example using Jest
import { takeLatest } from 'redux-saga/effects';
import { types } from 'modules/review/reducer';
import SagaTester from 'redux-saga-tester';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import watchRequestReviews, { requestReviews } from '../reviews';
const mockAxios = new MockAdapter(axios);
describe('(Saga) Reviews', () => {
afterEach(() => {
mockAxios.reset();
});
it('should received reviews', async () => {
const services = [
{
title: 'Title',
description: 'Description',
},
];
const responseData = [{
id: '595bdb2204b1aa3a7b737165',
services,
}];
mockAxios.onGet('/api/reviews?filter[where][locale]=en').reply(200, responseData);
// Start up the saga tester
const sagaTester = new SagaTester({ initialState: { reviews: [] } });
sagaTester.start(watchRequestReviews);
// Dispatch the event to start the saga
sagaTester.dispatch({ type: types.REVIEWS_REQUEST, locale: 'en' });
// Hook into the success action
await sagaTester.waitFor(types.REVIEWS_RECEIVE);
expect(sagaTester.getLatestCalledAction()).toEqual({
type: types.REVIEWS_RECEIVE,
payload: { en: services },
});
});
});

Why is my immutable Redux store not the same in UI and test?

I'm building up an AppHeader component that connects to a Redux store to get its props. Ideally, I'd probably do it different, but decided to use as an exercise in testing a connected component (I'm new to React).
I'm trying to test this component by using redux-mock-store, but when I get it working in tests, the UI fails. When I get it working in the UI, the tests fail.
The PROBLEM_LINE in AppHeader.component.js below, is where the symptom originates.
When set to appHeader: state.get('AppHeader'), the tests pass successfully, but the console shows:
Uncaught TypeError: state.get is not a function at
Function.mapStateToProps [as mapToProps]`
When set to appHeader: state.AppHeader, the UI correctly displays "Real App Title" inside of an but the test now throws:
TypeError: Cannot read property 'toJS' of undefined
This seems to be an issue with using Immutable.js structures. If I change both initialState variables using plain JS objects, the tests pass and the correct value is displayed. I feel like I must be using immutable incorrectly, or am not getting stores set up correctly with it.
I've read nearly all the posts returned by Google re: testing connected components/containers but most were using state.AppHeader or state.get('AppHeader') in mapStateToProps, and most except a precious few were more on how to hook up redux and react but not so much testing it.
I tried to forego using redux-mock-store, and creating my own store (i.e. a function with dispatch, subscribe, etc) but that didn't solve any problems and only created new ones. Maybe I need to reinvestigate that if that's a better way in the end.
src/index.js
import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux'
import {Router, browserHistory} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
import configureStore from './store/configureStore';
import routes from './routes';
const store = configureStore;
const history = syncHistoryWithStore(browserHistory, store);
render(
<Provider store={store}>
<Router history={history} routes={routes} />
</Provider>,
document.getElementById('root')
);
src/store/configureStore.js
import {createStore, combineReducers} from 'redux';
import {routerReducer} from 'react-router-redux';
import * as reducers from '../ducks';
const rootReducer = combineReducers({
routing: routerReducer,
...reducers
});
export default createStore(
rootReducer
);
src/ducks/index.js
import AppHeader from './AppHeader.duck';
export {
AppHeader
};
src/ducks/AppHeader.duck.js
import {fromJS} from 'immutable';
////////////
/// Actions
const SET_APP_TITLE = 'new-react/AppHeader/SET_APP_TITLE';
////////////
/// Reducer
const initialState = fromJS({ <-- USING IMMUTABLE HERE
title: 'Real App Title'
});
export default function reducer(state = initialState, action){
switch(action.type){
case SET_APP_TITLE:
return state.set('title', action.payload.title);
default:
return state;
}
}
////////////
/// Action Creators
export function setAppTitle(title){
return {
type: SET_APP_TITLE,
payload: {title}
};
}
module.exports = reducer;
src/components/AppHeader.component.js
import React from 'react';
import {connect} from 'react-redux';
export class AppHeader extends React.PureComponent {
render() {
const appHeader = this.props.appHeader;
return (
<div className="appHeader">
<h1 className="appHeader_title">
{appHeader.title}
</h1>
</div>
);
}
}
AppHeader.propTypes = {
appHeader: React.PropTypes.object
};
const mapStateToProps = state => {
return {
appHeader: state.AppHeader.toJS() <-- PROBLEM LINE
};
}
export default connect(
mapStateToProps
)(AppHeader);
src/components/AppHeader.component.spec.js
import React from 'react';
import ReactDOM from 'react-dom';
import {mount} from 'enzyme';
import {Provider} from 'react-redux';
import configureMockStore from 'redux-mock-store'
import {fromJS} from 'immutable';
import AppHeader from './AppHeader';
let initialState = fromJS({ <-- USING IMMUTABLE AGAIN
AppHeader: {
title: 'Mock App Title'
}
});
const mockStore = configureMockStore([])(initialState);
describe('AppHeader', () => {
let component;
beforeEach(() => {
const wrapper = mount(
<Provider store={mockStore}>
<AppHeader />
</Provider>
);
component = wrapper.find('AppHeader');
});
it('renders without crashing', () => {
expect(component).toBeDefined();
});
it('shows the app title', () => {
expect(component.find('.appHeader_title').text())
.toBe('App Header Title');
});
});
All dependencies were installed in the last day or so, so are the latest version from npm install. The app itself was created with create-react-app.

Enzyme object has no useful methods .to .length

I have the following test
import test from 'ava';
import React from 'react';
import { shallow } from 'enzyme';
import A from '../../../src/components/A/index';
import B from '../../../src/components/B/index';
console.log('loaded component test');
test('shallow', t => {
const wrapper = shallow(<A />);
t.is(wrapper.find(B).length, 38);
});
Component A is a listing of several component Bs. What could I be doing wrong? I'm using Enzyme and AVA.
Warning: A: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://facebook/react-special-props)
Warning: A: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://facebook/react-special-props)
t.is(wrapper.find(B).length, 38)
|
0
1 test failed [16:55:47]
1 uncaught exception
1. components › A › index › shallow
AssertionError: 0 === 38
Test.fn (index.js:11:5)
The problem was that I needed a full DOM in order test these nested components. I added
/** setup.js
* provides a document for enzyme mount tests
* simply require this file to make the environment available
*/
import test from 'ava';
var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
test(t => {
t.pass('DOM setup'); // silences ava warning
});
and then I utilized mount
import test from 'ava';
import React from 'react';
import { mount, shallow } from 'enzyme';
import setup from '../../setup';
import A from '../../../src/components/A/index';
import B from '../../../src/components/B/index';
test('should display no B if A given no props', t => {
const wrapper = mount(<A />);
t.is(wrapper.find(B).length, 0);
});
test('should display two Bs if A given two B via props', t => {
const testBs = [
{ id: 1},
{ id: 2},
];
const wrapper = mount(<A Bees={testBs} />);
t.is(wrapper.find(B).length, 2);
});
Note I did not use the jsx syntax when passing B into find(). It also helped to upgrade ava, babel-core versions and add the es2017 preset to get async/await support.

Find component by display name when the component is stateless functional, with Enzyme

I have the following components:
// Hello.js
export default (React) => ({name}) => {
return (
<div>
Hello {name ? name : 'Stranger'}!
</div>
)
}
// App.js
import createHello from './Hello'
export default (React) => () => {
const Hello = createHello(React)
const helloProps = {
name: 'Jane'
}
return (
<Hello { ...helloProps } />
)
}
// index.js
import React from 'react'
import { render } from 'react-dom'
import createApp from './App'
const App = createApp(React)
render(
<App />,
document.getElementById('app')
)
And I want to set up a test to see if the App component contains one Hello component. I tried the following, using Tape and Enzyme:
import createApp from './App'
import React from 'react'
import test from 'tape'
import { shallow } from 'enzyme'
test('App component test', (assert) => {
const App = createApp(React)
const wrapper = shallow(<App />)
assert.equal(wrapper.find('Hello').length === 1, true)
})
But the result was that the length property of the find result was equal to 0, when I was expecting it to be equal to 1. So, how do I find my Hello component?
There are a couple of things you can do in this case. Enzyme can match component constructors based on the constructor's static .displayName or .name properties, or by referential equality. As a result, the following approaches should all work:
Direct Reference
you can import the actual components in your tests and find them using direct references to the component:
// NavBar-test.js
import NavBar from './path/to/NavBar';
...
wrapper.find(NavBar).length)
Named Function Expressions
If you use named function expressions to create your stateless functional components, the names should still work.
// NavBar.js
module.exports = function NavBar(props) { ... }
Static .displayName property
You can add a static .displayName property on the components:
// NavBar.js
const NavBar = (props) => { ... };
NavBar.displayName = 'NavBar';
Try to import the Hello component in the top of your file and then update your assertion to find the actual component and not the name of it. Like below:
import createApp from './App'
import Hello from './Hello'
import React from 'react'
import test from 'tape'
import { shallow } from 'enzyme'
test('App component test', (assert) => {
const App = createApp(React)
const wrapper = shallow(<App />)
assert.equal(wrapper.find(Hello).length === 1, true)
})
Btw for all the enzyme users out there the assertion would be something like:
expect(wrapper.find(Hello)).toHaveLength(1);