it('changes clientApp when different site link clicked', () => { const dispatchSpy = sinon.spy(_store, 'dispatch'); const fakeEvent = createFakeEvent({ currentTarget: { getAttribute: (attribute) => { if (attribute === 'data-clientapp') { return CLIENT_APP_ANDROID; } if (attribute === 'href') { return `/en-US/${CLIENT_APP_ANDROID}/`; } return undefined; }, }, }); const getAttributeSpy = sinon.spy(fakeEvent.currentTarget, 'getAttribute'); const fakeHistory = createFakeHistory(); _store.dispatch(setClientApp(CLIENT_APP_FIREFOX)); const root = render({ history: fakeHistory }); root.find(`.SectionLinks-clientApp-android`).simulate('click', fakeEvent); sinon.assert.called(fakeEvent.preventDefault); sinon.assert.calledWith(getAttributeSpy, 'data-clientapp'); sinon.assert.calledWith(getAttributeSpy, 'href'); sinon.assert.calledWith(dispatchSpy, setClientApp(CLIENT_APP_ANDROID)); sinon.assert.calledWith(fakeHistory.push, `/en-US/${CLIENT_APP_ANDROID}/`); });
it('maps state to props', () => { const { store } = createStore(); store.dispatch(setClientApp('android')); store.dispatch(setLang('fr')); store.dispatch(categoriesLoad(categoriesResponse)); const props = mapStateToProps(store.getState(), { params: { visibleAddonType: 'extensions' }, }); expect(props).toEqual({ addonType: ADDON_TYPE_EXTENSION, categories: { Games: { application: 'android', name: 'Games', slug: 'Games', type: ADDON_TYPE_EXTENSION, }, travel: { application: 'android', name: 'Travel', slug: 'travel', type: ADDON_TYPE_EXTENSION, }, }, clientApp: 'android', error: false, loading: false, }); });
it('stores the clientApp', () => { const existingState = { bar: 'baz' }; const clientApp = 'firefox'; assert.deepEqual( api(existingState, actions.setClientApp(clientApp)), { ...existingState, clientApp }); });
it('stores the clientApp', () => { const existingState = { bar: 'baz' }; const clientApp = 'firefox'; expect(api(existingState, actions.setClientApp(clientApp))).toEqual({ ...existingState, clientApp, }); });
it('renders a link to the Language Tools page for non-Android clients', () => { _store.dispatch(setClientApp(CLIENT_APP_FIREFOX)); const root = render({ viewContext: ADDON_TYPE_EXTENSION }); expect( root.find('.SectionLinks-dropdownlink').findWhere((element) => { return element.prop('to') === '/language-tools/'; }), ).toHaveProp('children', 'Dictionaries & Language Packs'); });
it('hides link to the Language Tools page on Android clients', () => { _store.dispatch(setClientApp(CLIENT_APP_ANDROID)); const root = render({ viewContext: ADDON_TYPE_EXTENSION }); expect( root.find('.SectionLinks-dropdownlink').findWhere((element) => { return element.prop('to') === '/language-tools/'; }), ).toHaveLength(0); });
it('renders Language Tools active when viewContext is languageTools', () => { _store.dispatch(setClientApp(CLIENT_APP_FIREFOX)); _store.dispatch(setViewContext(VIEW_CONTEXT_LANGUAGE_TOOLS)); const root = render(); expect( root.find('.SectionLinks-dropdownlink--active').children(), ).toIncludeText('Dictionaries & Language Packs'); });
function renderHeader({ ...props }) { const { store } = createStore(); store.dispatch(setClientApp('android')); store.dispatch(setLang('en-GB')); const fakeI18n = getFakeI18nInst(); return findRenderedComponentWithType(renderIntoDocument( <Provider store={store}> <I18nProvider i18n={fakeI18n}> <HeaderBase i18n={fakeI18n} {...props} /> </I18nProvider> </Provider> ), HeaderBase); }
export function dispatchClientMetadata({ store = createStore().store, clientApp = 'android', lang = 'en-US', userAgent = sampleUserAgent, } = {}) { store.dispatch(setClientApp(clientApp)); store.dispatch(setLang(lang)); store.dispatch(setUserAgent(userAgent)); return { store, state: store.getState(), }; }
it('shows Android name and hides link in header on Android', () => { _store.dispatch(setClientApp(CLIENT_APP_ANDROID)); const root = render(); expect( root .find('.SectionLinks-subheader') .at(0) .children(), ).toIncludeText('for Android'); expect( root.find(`.SectionLinks-clientApp-${CLIENT_APP_ANDROID}`), ).toHaveLength(0); expect( root.find(`.SectionLinks-clientApp-${CLIENT_APP_FIREFOX}`), ).toHaveLength(1); });
beforeEach(() => { errorHandler = createStubErrorHandler(); store = createStore().store; store.dispatch(setClientApp('firefox')); store.dispatch(setLang('en-US')); const state = store.getState(); initialState = { api: state.api, categories: state.categories, }; sagaTester = new SagaTester({ initialState, reducers: { api: apiReducer, categories: categoriesReducer }, }); sagaTester.start(categoriesSaga); });
function render({ ...props }) { const { store } = createStore(); store.dispatch(setClientApp('android')); store.dispatch(setLang('fr')); store.dispatch(categoriesLoad(categoriesResponse)); const { categories } = store.getState().categories; const baseProps = { clientApp: store.getState().api.clientApp, categories: categories[CLIENT_APP_ANDROID][ADDON_TYPE_EXTENSION], dispatch: sinon.stub(), }; return findRenderedComponentWithType(renderIntoDocument( <Provider store={store}> <CategoriesBase i18n={getFakeI18nInst()} {...baseProps} {...props} /> </Provider> ), CategoriesBase); }
expect(() => actions.setClientApp('')).toThrowError(/cannot be falsey/);
it('creates the SET_CLIENT_APP action', () => { expect(actions.setClientApp('firefox')).toEqual({ type: 'SET_CLIENT_APP', payload: { clientApp: 'firefox' }, }); });
match({ location: req.url, routes }, ( err, redirectLocation, renderProps ) => { cookie.plugToRequest(req, res); if (err) { log.error({ err, req }); return showErrorPage(res, 500); } if (!renderProps) { return showErrorPage(res, 404); } const store = createStore(); const token = cookie.load(config.get('cookieName')); if (token) { store.dispatch(setJwt(token)); } // Get SRI for deployed services only. const sriData = (isDeployed) ? JSON.parse( fs.readFileSync(path.join(config.get('basePath'), 'dist/sri.json')) ) : {}; // Check the lang supplied by res.locals.lang for validity // or fall-back to the default. const lang = isValidLang(res.locals.lang) ? res.locals.lang : config.get('defaultLang'); const dir = getDirection(lang); const locale = langToLocale(lang); store.dispatch(setLang(lang)); if (res.locals.clientApp) { store.dispatch(setClientApp(res.locals.clientApp)); } else { log.warn(`No clientApp for this URL: ${req.url}`); } function hydrateOnClient(props = {}) { const pageProps = { appName: appInstanceName, assets: webpackIsomorphicTools.assets(), htmlLang: lang, htmlDir: dir, includeSri: isDeployed, noScriptStyles, sriData, store, trackingEnabled: convertBoolean(config.get('trackingEnabled')), ...props, }; const HTML = ReactDOM.renderToString( <ServerHtml {...pageProps} />); res.send(`<!DOCTYPE html>\n${HTML}`); } // Set disableSSR to true to debug // client-side-only render. if (config.get('disableSSR') === true) { return Promise.resolve(hydrateOnClient()); } return loadOnServer({ ...renderProps, store }) .then(() => { // eslint-disable-next-line global-require let i18nData = {}; try { if (locale !== langToLocale(config.get('defaultLang'))) { // eslint-disable-next-line global-require, import/no-dynamic-require i18nData = require( `../../locale/${locale}/${appInstanceName}.js`); } } catch (e) { log.info( `Locale JSON not found or required for locale: "${locale}"`); log.info( `Falling back to default lang: "${config.get('defaultLang')}".`); } const i18n = makeI18n(i18nData, lang); const InitialComponent = ( <I18nProvider i18n={i18n}> <Provider store={store} key="provider"> <ReduxAsyncConnect {...renderProps} /> </Provider> </I18nProvider> ); const asyncConnectLoadState = store.getState().reduxAsyncConnect.loadState || {}; const reduxResult = getReduxConnectError(asyncConnectLoadState); if (reduxResult.status) { return showErrorPage(res, reduxResult.status); } return hydrateOnClient({ component: InitialComponent }); }) .catch((error) => { log.error({ err: error }); return showErrorPage(res, 500); }); });
beforeEach(() => { store = createStore().store; store.dispatch(setClientApp('android')); store.dispatch(setLang('fr')); });