Skip to content

Commit

Permalink
fix(testing): mock window location, components with hooks (#680)
Browse files Browse the repository at this point in the history
* i18n remove load hook comp
* tests, mock window location
  • Loading branch information
cdcabrera committed Jul 13, 2021
1 parent 67843b1 commit 2706c44
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 81 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"plugin:jest/recommended",
"plugin:jsdoc/recommended"
],
"globals": {
"mountHookComponent": "readonly",
"mockWindowLocation": "readonly"
},
"rules": {
"arrow-parens": [
"error",
Expand Down
32 changes: 9 additions & 23 deletions src/components/i18n/__tests__/i18n.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { readFileSync } from 'fs';
import glob from 'glob';
import React from 'react';
import { act } from 'react-dom/test-utils';
import PropTypes from 'prop-types';
import { mount, shallow } from 'enzyme';
import { shallow } from 'enzyme';
import _get from 'lodash/get';
import enLocales from '@curiosity/locales/en-US';
import { I18n, translate, translateComponent } from '../i18n';
Expand Down Expand Up @@ -53,38 +52,25 @@ const getTranslationKeys = ({ files = './src/**/!(*.test|*.spec).@(js|jsx)', lis
describe('I18n Component', () => {
const getKeys = getTranslationKeys({});

const loadHookComponent = async callback => {
let component = null;
await act(async () => {
component = callback();
});
component?.update();
return component;
};

it('should render a basic component', async () => {
const props = {
locale: 'es'
};

const component = await loadHookComponent(() =>
mount(
<I18n {...props}>
<React.Fragment>lorem ipsum</React.Fragment>
</I18n>
)
const component = await mountHookComponent(
<I18n {...props}>
<React.Fragment>lorem ipsum</React.Fragment>
</I18n>
);

expect(component).toMatchSnapshot('basic');
});

it('should pass children', async () => {
const component = await loadHookComponent(() =>
mount(
<I18n>
<React.Fragment>lorem ipsum</React.Fragment>
</I18n>
)
const component = await mountHookComponent(
<I18n>
<React.Fragment>lorem ipsum</React.Fragment>
</I18n>
);

expect(component.html()).toMatchSnapshot('children');
Expand Down
26 changes: 6 additions & 20 deletions src/components/productView/__tests__/productViewMissing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@ import { shallow } from 'enzyme';
import { ProductViewMissing } from '../productViewMissing';

describe('ProductViewMissing Component', () => {
const mockWindowLocation = async ({ url, callback }) => {
const updatedUrl = new URL(url);
const { location } = window;
delete window.location;
// mock
window.location = {
href: updatedUrl.href,
search: updatedUrl.search,
hash: updatedUrl.hash,
pathname: updatedUrl.pathname
};
await callback();
// restore
window.location = location;
};

it('should render a non-connected component', () => {
const props = {};
const component = shallow(<ProductViewMissing {...props} />);
Expand All @@ -28,12 +12,14 @@ describe('ProductViewMissing Component', () => {
it('should render a predictable set of product cards', () => {
const props = {};

mockWindowLocation({
url: 'https://ci.foo.redhat.com/loremIpsum/dolorSit/',
callback: () => {
mockWindowLocation(
() => {
const component = shallow(<ProductViewMissing {...props} />);
expect(component).toMatchSnapshot('non-connected');
},
{
url: 'https://ci.foo.redhat.com/loremIpsum/dolorSit/'
}
});
);
});
});
66 changes: 30 additions & 36 deletions src/components/router/__tests__/routerHelpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,6 @@ import {
} from '../routerHelpers';

describe('RouterHelpers', () => {
const mockWindowLocation = async ({ url, callback }) => {
const updatedUrl = new URL(url);
const { location } = window;
delete window.location;
// mock
window.location = {
href: updatedUrl.href,
search: updatedUrl.search,
hash: updatedUrl.hash,
pathname: updatedUrl.pathname
};
await callback();
// restore
window.location = location;
};

it('should return specific properties', () => {
expect(routerHelpers).toMatchSnapshot('routerHelpers');
});
Expand Down Expand Up @@ -156,14 +140,16 @@ describe('RouterHelpers', () => {
});

it('should return default navigation and route details', () => {
mockWindowLocation({
url: 'https://ci.foo.redhat.com/loremIpsum/dolorSit/',
callback: () => {
mockWindowLocation(
() => {
expect({
navRoute: getRouteConfigByPath()
}).toMatchSnapshot('detail: defaults');
},
{
url: 'https://ci.foo.redhat.com/loremIpsum/dolorSit/'
}
});
);
});

it('should return navigation and route details from a path', () => {
Expand Down Expand Up @@ -191,41 +177,49 @@ describe('RouterHelpers', () => {
});

it('should handle location search and hash passthrough values', () => {
mockWindowLocation({
url: 'https://ci.foo.redhat.com/subscriptions/rhel',
callback: () => {
mockWindowLocation(
() => {
expect({
routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
}).toMatchSnapshot('NO search and hash');
},
{
url: 'https://ci.foo.redhat.com/subscriptions/rhel'
}
});
);

mockWindowLocation({
url: 'https://ci.foo.redhat.com/subscriptions/rhel?dolor=sit',
callback: () => {
mockWindowLocation(
() => {
expect({
routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
}).toMatchSnapshot('search');
},
{
url: 'https://ci.foo.redhat.com/subscriptions/rhel?dolor=sit'
}
});
);

mockWindowLocation({
url: 'https://ci.foo.redhat.com/subscriptions/rhel#lorem',
callback: () => {
mockWindowLocation(
() => {
expect({
routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
}).toMatchSnapshot('hash');
},
{
url: 'https://ci.foo.redhat.com/subscriptions/rhel#lorem'
}
});
);

mockWindowLocation({
url: 'https://ci.foo.redhat.com/subscriptions/rhel?dolor=sit#lorem',
callback: () => {
mockWindowLocation(
() => {
expect({
routeHref: getRouteConfig({ pathName: '/rhel' }).routeHref
}).toMatchSnapshot('search and hash');
},
{
url: 'https://ci.foo.redhat.com/subscriptions/rhel?dolor=sit#lorem'
}
});
);
});

it('should return a lazy loaded view for every route', () => {
Expand Down
52 changes: 51 additions & 1 deletion src/setupTests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { configure } from 'enzyme';
import { configure, mount } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import { act } from 'react-dom/test-utils';
import * as pfReactCoreComponents from '@patternfly/react-core';
import * as pfReactChartComponents from '@patternfly/react-charts';

Expand All @@ -17,6 +18,9 @@ jest.mock('i18next', () => {
return new Test();
});

/**
* Emulate for component checks
*/
jest.mock('lodash/debounce', () => jest.fn);

/**
Expand All @@ -42,6 +46,12 @@ const addDisplayName = components => {
addDisplayName(pfReactCoreComponents);
addDisplayName(pfReactChartComponents);

/**
* Apply a global insights chroming object.
*
* @type {{chrome: {init: Function, navigation: Function, auth: {getUser: Function}, identifyApp: Function,
* getUserPermissions: Function, isBeta: Function, hideGlobalFilter: Function, on: Function}}}
*/
global.window.insights = {
chrome: {
auth: {
Expand All @@ -65,6 +75,46 @@ global.window.insights = {
}
};

/**
* Enzyme for components using hooks.
*
* @param {Node} component
*
* @returns {Promise<null>}
*/
global.mountHookComponent = async component => {
let mountedComponent = null;
await act(async () => {
mountedComponent = mount(component);
});
mountedComponent?.update();
return mountedComponent;
};

/**
* Generate a mock window location object, allow async.
*
* @param {Function} callback
* @param {object} options
* @param {string} options.url
* @returns {Promise<void>}
*/
global.mockWindowLocation = async (callback, { url = 'https://ci.foo.redhat.com/subscriptions/rhel' } = {}) => {
const updatedUrl = new URL(url);
const { location } = window;
delete window.location;
// mock
window.location = {
href: updatedUrl.href,
search: updatedUrl.search,
hash: updatedUrl.hash,
pathname: updatedUrl.pathname
};
await callback();
// restore
window.location = location;
};

/*
* For applying a global Jest "beforeAll", based on
* jest-prop-type-error, https://www.npmjs.com/package/jest-prop-type-error
Expand Down
2 changes: 1 addition & 1 deletion tests/__snapshots__/code.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ Array [
"components/inventorySubscriptions/inventorySubscriptions.js:60: console.warn(\`Sorting can only be performed on select fields, confirm field \${id} is allowed.\`);",
"redux/common/reduxHelpers.js:250: console.error(\`Error: Property \${prop} does not exist within the passed state.\`, state);",
"redux/common/reduxHelpers.js:254: console.warn(\`Warning: Property \${prop} does not exist within the passed initialState.\`, initialState);",
"setupTests.js:75: console.error = (message, ...args) => {",
"setupTests.js:125: console.error = (message, ...args) => {",
]
`;

0 comments on commit 2706c44

Please sign in to comment.