--- jetpack modules actions ---
/** @format */

/**
 * External dependencies
 */

import { omit, mapValues } from 'lodash';

/**
 * Internal dependencies
 */
import {
        JETPACK_MODULE_ACTIVATE,
        JETPACK_MODULE_ACTIVATE_FAILURE,
        JETPACK_MODULE_ACTIVATE_SUCCESS,
        JETPACK_MODULE_DEACTIVATE,
        JETPACK_MODULE_DEACTIVATE_FAILURE,
        JETPACK_MODULE_DEACTIVATE_SUCCESS,
        JETPACK_MODULES_RECEIVE,
        JETPACK_MODULES_REQUEST,
        JETPACK_MODULES_REQUEST_FAILURE,
        JETPACK_MODULES_REQUEST_SUCCESS,
} from 'state/action-types';
import wp from 'lib/wp';

export const activateModule = ( siteId, moduleSlug, silent = false ) => {
        return dispatch => {
                dispatch( {
                        type: JETPACK_MODULE_ACTIVATE,
                        siteId,
                        moduleSlug,
                        silent,
                } );

                return wp
                        .undocumented()
                        .jetpackModuleActivate( siteId, moduleSlug )
                        .then( () => {
                                dispatch( {
                                        type: JETPACK_MODULE_ACTIVATE_SUCCESS,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                } );
                        } )
                        .catch( error => {
                                dispatch( {
                                        type: JETPACK_MODULE_ACTIVATE_FAILURE,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                        error: error.message,
                                } );
                        } );
        };
};

export const deactivateModule = ( siteId, moduleSlug, silent = false ) => {
        return dispatch => {
                dispatch( {
                        type: JETPACK_MODULE_DEACTIVATE,
                        siteId,
                        moduleSlug,
                        silent,
                } );

                return wp
                        .undocumented()
                        .jetpackModuleDeactivate( siteId, moduleSlug )
                        .then( () => {
                                dispatch( {
                                        type: JETPACK_MODULE_DEACTIVATE_SUCCESS,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                } );
                        } )
                        .catch( error => {
                                dispatch( {
                                        type: JETPACK_MODULE_DEACTIVATE_FAILURE,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                        error: error.message,
                                } );
                        } );
        };
};

/**
 * Returns an action object used in signalling that the available modules
 * in a Jetpack site were received.
 *
 * @param  {Number}   siteId    Site ID
 * @param  {Object[]} modules Object of modules indexed by slug
 * @return {Object}             Action object
 */
export function receiveJetpackModules( siteId, modules ) {
        return {
                type: JETPACK_MODULES_RECEIVE,
                siteId,
                modules,
        };
}

export const fetchModuleList = siteId => {
        return dispatch => {
                dispatch( {
                        type: JETPACK_MODULES_REQUEST,
                        siteId,
                } );

                return wp
                        .undocumented()
                        .getJetpackModules( siteId )
                        .then( ( { data } ) => {
                                const modules = mapValues( data, module => ( {
                                        active: module.activated,
                                        ...omit( module, 'activated' ),
                                } ) );

                                dispatch( receiveJetpackModules( siteId, modules ) );
                                dispatch( {
                                        type: JETPACK_MODULES_REQUEST_SUCCESS,
                                        siteId,
                                } );
                        } )
                        .catch( error => {
                                dispatch( {
                                        type: JETPACK_MODULES_REQUEST_FAILURE,
                                        siteId,
                                        error: error.message,
                                } );
                        } );
        };
};
--- module reducers/selectors ---
--- client/state/jetpack/modules/actions.js
/** @format */

/**
 * External dependencies
 */

import { omit, mapValues } from 'lodash';

/**
 * Internal dependencies
 */
import {
        JETPACK_MODULE_ACTIVATE,
        JETPACK_MODULE_ACTIVATE_FAILURE,
        JETPACK_MODULE_ACTIVATE_SUCCESS,
        JETPACK_MODULE_DEACTIVATE,
        JETPACK_MODULE_DEACTIVATE_FAILURE,
        JETPACK_MODULE_DEACTIVATE_SUCCESS,
        JETPACK_MODULES_RECEIVE,
        JETPACK_MODULES_REQUEST,
        JETPACK_MODULES_REQUEST_FAILURE,
        JETPACK_MODULES_REQUEST_SUCCESS,
} from 'state/action-types';
import wp from 'lib/wp';

export const activateModule = ( siteId, moduleSlug, silent = false ) => {
        return dispatch => {
                dispatch( {
                        type: JETPACK_MODULE_ACTIVATE,
                        siteId,
                        moduleSlug,
                        silent,
                } );

                return wp
                        .undocumented()
                        .jetpackModuleActivate( siteId, moduleSlug )
                        .then( () => {
                                dispatch( {
                                        type: JETPACK_MODULE_ACTIVATE_SUCCESS,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                } );
                        } )
                        .catch( error => {
                                dispatch( {
                                        type: JETPACK_MODULE_ACTIVATE_FAILURE,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                        error: error.message,
                                } );
                        } );
        };
};

export const deactivateModule = ( siteId, moduleSlug, silent = false ) => {
        return dispatch => {
                dispatch( {
                        type: JETPACK_MODULE_DEACTIVATE,
                        siteId,
                        moduleSlug,
                        silent,
                } );

                return wp
                        .undocumented()
                        .jetpackModuleDeactivate( siteId, moduleSlug )
                        .then( () => {
                                dispatch( {
                                        type: JETPACK_MODULE_DEACTIVATE_SUCCESS,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                } );
                        } )
                        .catch( error => {
                                dispatch( {
                                        type: JETPACK_MODULE_DEACTIVATE_FAILURE,
                                        siteId,
                                        moduleSlug,
                                        silent,
                                        error: error.message,
                                } );
                        } );
        };
};

/**
 * Returns an action object used in signalling that the available modules
 * in a Jetpack site were received.
 *
 * @param  {Number}   siteId    Site ID
 * @param  {Object[]} modules Object of modules indexed by slug
 * @return {Object}             Action object
 */
export function receiveJetpackModules( siteId, modules ) {
        return {
                type: JETPACK_MODULES_RECEIVE,
                siteId,
                modules,
        };
}

export const fetchModuleList = siteId => {
        return dispatch => {
                dispatch( {
                        type: JETPACK_MODULES_REQUEST,
                        siteId,
                } );

                return wp
                        .undocumented()
                        .getJetpackModules( siteId )
                        .then( ( { data } ) => {
                                const modules = mapValues( data, module => ( {
                                        active: module.activated,
                                        ...omit( module, 'activated' ),
                                } ) );

                                dispatch( receiveJetpackModules( siteId, modules ) );
                                dispatch( {
                                        type: JETPACK_MODULES_REQUEST_SUCCESS,
                                        siteId,
                                } );
                        } )
                        .catch( error => {
                                dispatch( {
                                        type: JETPACK_MODULES_REQUEST_FAILURE,
                                        siteId,
                                        error: error.message,
                                } );
                        } );
        };
};
--- client/state/jetpack/modules/reducer.js
/** @format */

/**
 * External dependencies
 */

import { forEach, get, merge, pickBy } from 'lodash';

/**
 * Internal dependencies
 */
import {
        JETPACK_MODULE_ACTIVATE,
        JETPACK_MODULE_ACTIVATE_FAILURE,
        JETPACK_MODULE_ACTIVATE_SUCCESS,
        JETPACK_MODULE_DEACTIVATE,
        JETPACK_MODULE_DEACTIVATE_FAILURE,
        JETPACK_MODULE_DEACTIVATE_SUCCESS,
        JETPACK_MODULES_RECEIVE,
        JETPACK_MODULES_REQUEST,
        JETPACK_MODULES_REQUEST_FAILURE,
        JETPACK_MODULES_REQUEST_SUCCESS,
        JETPACK_SETTINGS_RECEIVE,
        JETPACK_SETTINGS_UPDATE_SUCCESS,
} from 'state/action-types';
import { combineReducers, createReducer } from 'state/utils';

const createItemsReducer = active => {
        return ( state, { siteId, moduleSlug } ) => {
                return merge( {}, state, {
                        [ siteId ]: {
                                [ moduleSlug ]: {
                                        active,
                                },
                        },
                } );
        };
};

const createItemsListReducer = () => {
        return ( state, { siteId, modules } ) => {
                return merge( {}, state, {
                        [ siteId ]: modules,
                } );
        };
};

const createRequestsReducer = data => {
        return ( state, { siteId, moduleSlug } ) => {
                return merge( {}, state, {
                        [ siteId ]: {
                                [ moduleSlug ]: data,
                        },
                } );
        };
};

const createModuleListRequestReducer = fetchingModules => {
        return ( state, { siteId } ) => {
                return merge( {}, state, {
                        [ siteId ]: {
                                fetchingModules,
                        },
                } );
        };
};

const createSettingsItemsReducer = () => {
        return ( state, { siteId, settings } ) => {
                let updatedState = state;
                const moduleActivationState = pickBy( settings, ( settingValue, settingName ) => {
                        return get( state, [ siteId, settingName ] ) !== undefined;
                } );

                forEach( moduleActivationState, ( active, moduleSlug ) => {
                        updatedState = Object.assign( {}, updatedState, {
                                [ siteId ]: {
                                        ...updatedState[ siteId ],
                                        [ moduleSlug ]: {
                                                ...updatedState[ siteId ][ moduleSlug ],
                                                active,
                                        },
                                },
                        } );
                } );

                return updatedState;
        };
};

/**
 * `Reducer` function which handles request/response actions
 * concerning Jetpack modules data updates
 *
 * @param  {Array}  state  Current state
 * @param  {Object} action action
 * @return {Array}         Updated state
 */
export const items = createReducer(
        {},
        {
                [ JETPACK_MODULE_ACTIVATE_SUCCESS ]: createItemsReducer( true ),
                [ JETPACK_MODULE_DEACTIVATE_SUCCESS ]: createItemsReducer( false ),
                [ JETPACK_MODULES_RECEIVE ]: createItemsListReducer(),
                [ JETPACK_SETTINGS_RECEIVE ]: createSettingsItemsReducer(),
                [ JETPACK_SETTINGS_UPDATE_SUCCESS ]: createSettingsItemsReducer(),
        }
);

/**
 * `Reducer` function which handles request/response actions
 * concerning Jetpack modules-related requests
 *
 * @param {Object} state - current state
 * @param {Object} action - action
 * @return {Object} updated state
 */
export const requests = createReducer(
        {},
        {
                [ JETPACK_MODULE_ACTIVATE ]: createRequestsReducer( { activating: true } ),
                [ JETPACK_MODULE_ACTIVATE_FAILURE ]: createRequestsReducer( { activating: false } ),
                [ JETPACK_MODULE_ACTIVATE_SUCCESS ]: createRequestsReducer( { activating: false } ),
                [ JETPACK_MODULE_DEACTIVATE ]: createRequestsReducer( { deactivating: true } ),
                [ JETPACK_MODULE_DEACTIVATE_FAILURE ]: createRequestsReducer( { deactivating: false } ),
                [ JETPACK_MODULE_DEACTIVATE_SUCCESS ]: createRequestsReducer( { deactivating: false } ),
                [ JETPACK_MODULES_REQUEST ]: createModuleListRequestReducer( true ),
                [ JETPACK_MODULES_REQUEST_FAILURE ]: createModuleListRequestReducer( false ),
                [ JETPACK_MODULES_REQUEST_SUCCESS ]: createModuleListRequestReducer( false ),
        }
);

export const reducer = combineReducers( {
        items,
        requests,
} );
--- client/state/jetpack/modules/test/actions.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';
import { omit, mapValues } from 'lodash';
<response clipped><NOTE>Due to the max output limit, only part of the full response has been shown to you.</NOTE>                         'module-b': {
                                                                module: 'module-b',
                                                                requires_connection: false,
                                                        },
                                                        'module-c': {
                                                                module: 'module-c',
                                                        },
                                                },
                                        },
                                },
                        },
                };

                const modules = getJetpackModulesRequiringConnection( stateTree, 12345678 );
                expect( modules ).to.eql( [ 'module-a' ] );
        } );
} );
--- client/state/selectors/test/get-jetpack-modules.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';

/**
 * Internal dependencies
 */
import { getJetpackModules } from 'state/selectors';
import { moduleData as MODULE_DATA_FIXTURE } from './fixtures/jetpack-modules';

describe( 'getJetpackModules()', () => {
        test( 'should return data for all modules for a known site', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                items: {
                                                        123456: MODULE_DATA_FIXTURE,
                                                },
                                        },
                                },
                        },
                        siteId = 123456;
                const output = getJetpackModules( stateIn, siteId );
                expect( output ).to.eql( MODULE_DATA_FIXTURE );
        } );

        test( 'should return null for an unknown site', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                items: {
                                                        654321: MODULE_DATA_FIXTURE,
                                                },
                                        },
                                },
                        },
                        siteId = 123456;
                const output = getJetpackModules( stateIn, siteId );
                expect( output ).to.be.null;
        } );
} );
--- client/state/selectors/test/is-activating-jetpack-module.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';

/**
 * Internal dependencies
 */
import { isActivatingJetpackModule } from 'state/selectors';
import { requests as REQUESTS_FIXTURE } from './fixtures/jetpack-modules';

describe( 'isActivatingJetpackModule()', () => {
        test( 'should return true if module is currently being activated', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isActivatingJetpackModule( stateIn, siteId, 'module-b' );
                expect( output ).to.be.true;
        } );

        test( 'should return false if module is currently not being activated', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isActivatingJetpackModule( stateIn, siteId, 'module-a' );
                expect( output ).to.be.false;
        } );

        test( 'should return null if that module is not known', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isActivatingJetpackModule( stateIn, siteId, 'module-z' );
                expect( output ).to.be.null;
        } );
} );
--- client/state/selectors/test/is-deactivating-jetpack-module.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';

/**
 * Internal dependencies
 */
import { isDeactivatingJetpackModule } from 'state/selectors';
import { requests as REQUESTS_FIXTURE } from './fixtures/jetpack-modules';

describe( 'isDeactivatingJetpackModule()', () => {
        test( 'should return true if module is currently being deactivated', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isDeactivatingJetpackModule( stateIn, siteId, 'module-a' );
                expect( output ).to.be.true;
        } );

        test( 'should return false if module is currently not being deactivated', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isDeactivatingJetpackModule( stateIn, siteId, 'module-b' );
                expect( output ).to.be.false;
        } );

        test( 'should return null if that module is not known', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isDeactivatingJetpackModule( stateIn, siteId, 'module-z' );
                expect( output ).to.be.null;
        } );
} );
--- client/state/selectors/test/is-fetching-jetpack-modules.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';

/**
 * Internal dependencies
 */
import { isFetchingJetpackModules } from 'state/selectors';
import { requests as REQUESTS_FIXTURE } from './fixtures/jetpack-modules';

describe( 'isFetchingJetpackModules()', () => {
        test( 'should return true if the list of modules is being fetched', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isFetchingJetpackModules( stateIn, siteId );
                expect( output ).to.be.true;
        } );

        test( 'should return false if the list of modules is currently not being fetched', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 654321;
                const output = isFetchingJetpackModules( stateIn, siteId );
                expect( output ).to.be.false;
        } );

        test( 'should return null if that site is not known', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                requests: REQUESTS_FIXTURE,
                                        },
                                },
                        },
                        siteId = 888888;
                const output = isFetchingJetpackModules( stateIn, siteId );
                expect( output ).to.be.null;
        } );
} );
--- client/state/selectors/test/is-jetpack-module-active.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';

/**
 * Internal dependencies
 */
import { isJetpackModuleActive } from 'state/selectors';
import { modules as MODULES_FIXTURE } from './fixtures/jetpack-modules';

describe( 'isJetpackModuleActive()', () => {
        test( 'should return true if the module is currently active', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                items: MODULES_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isJetpackModuleActive( stateIn, siteId, 'module-b' );
                expect( output ).to.be.true;
        } );

        test( 'should return false if the module is currently not active', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                items: MODULES_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isJetpackModuleActive( stateIn, siteId, 'module-a' );
                expect( output ).to.be.false;
        } );

        test( 'should return null if that module is not known', () => {
                const stateIn = {
                                jetpack: {
                                        modules: {
                                                items: MODULES_FIXTURE,
                                        },
                                },
                        },
                        siteId = 123456;
                const output = isJetpackModuleActive( stateIn, siteId, 'module-z' );
                expect( output ).to.be.null;
        } );
} );
--- client/state/selectors/test/is-jetpack-module-unavailable-in-development-mode.js
/** @format */

/**
 * External dependencies
 */
import { expect } from 'chai';

/**
 * Internal dependencies
 */
import { isJetpackModuleUnavailableInDevelopmentMode } from 'state/selectors';

describe( 'isJetpackModuleUnavailableInDevelopmentMode()', () => {
        test( 'should return null if the site modules are not known', () => {
                const stateTree = {
                        jetpack: {
                                modules: {
                                        items: {},
                                },
                        },
                };

                const unavailable = isJetpackModuleUnavailableInDevelopmentMode(
                        stateTree,
                        12345678,
                        'module-a'
                );
                expect( unavailable ).to.be.null;
        } );

        test( 'should return true for a module that requires connection', () => {
                const stateTree = {
                        jetpack: {
                                modules: {
                                        items: {
                                                12345678: {
                                                        'module-a': {
                                                                module: 'module-a',
                                                                requires_connection: true,
                                                        },
                                                },
                                        },
                                },
                        },
                };

                const unavailable = isJetpackModuleUnavailableInDevelopmentMode(
                        stateTree,
                        12345678,
                        'module-a'
                );
                expect( unavailable ).to.be.true;
        } );

        test( 'should return false for a module that does not require connection', () => {
                const stateTree = {
                        jetpack: {
                                modules: {
                                        items: {
                                                12345678: {
                                                        'module-a': {
                                                                module: 'module-a',
                                                                requires_connection: false,
                                                        },
                                                },
                                        },
                                },
                        },
                };

                const unavailable = isJetpackModuleUnavailableInDevelopmentMode(
                        stateTree,
                        12345678,
                        'module-a'
                );
                expect( unavailable ).to.be.false;
        } );

        test( 'should return false for a module that does not specify whether it requires connection', () => {
                const stateTree = {
                        jetpack: {
                                modules: {
                                        items: {
                                                12345678: {
                                                        'module-a': {
                                                                module: 'module-a',
                                                        },
                                                },
                                        },
                                },
                        },
                };

                const unavailable = isJetpackModuleUnavailableInDevelopmentMode(
                        stateTree,
                        12345678,
                        'module-a'
                );
                expect( unavailable ).to.be.false;
        } );
} );
[The command completed with exit code 0.]
[Current working directory: /workspace/wp-calypso]
[Python interpreter: /usr/bin/python]
[Command finished with exit code 0]