简体   繁体   中英

How to test window.open is being called from a react component

I have a react component that renders a series of other components, each with their own checkbox.

There is a state hook called rulesToDownload which begins as an empty array and has ids added to / removed from it as checkboxes are checked / unchecked.

When the 'download' button is clicked, the rulesToDownload array is passed to a data function DownloadFundDataById that forEach's over the array and calls window.open for each value with an api call with the id appended. The data function is imported into the component, not passed in as a prop.

This causes multiple tabs to flash up before closing when the data downloads. It's not perfect but it works.

I want to complete my test coverage and need to test that the function gets called on button click, and that it does what it should.

Any help appreciated.

Code below:

Summary.test.js:

 it(`should create correct download array when some rules are selected`, async () => {
        global.open = sandbox.spy();

        fetch.mockResponseOnce(JSON.stringify(selectedRules));

        wrapper = mount(<Summary/>);
        await act(async () => {} );
        wrapper.update();

        wrapper.find('ReportProgressSummary').first().find('input').last().simulate('change', {target: {checked: true}});

        wrapper.find('button').first().simulate('click');
        expect(global.open).to.have.been.called();

    });

I can confirm that all the 'find' statements are correct, and correctly update the checked value.

Summary.js:

const Summary = () => {
    const [expand, setExpand] = useState(false);
    const [buttonText, setButtonText] = useState("expand other rules");
    const [rulesToDownload, setRulesToDownload] = useState([]);
    const [data, setData] = useState([]);
    const [dataLoadComplete, setDataLoadComplete] = useState(false);
    const [dataLoadFailed, setDataLoadFailed] = useState(false);

    useEffect(() => {
            loadData();
    }, []);

    const loadData = async () => {
        try {
            let importedData = await ExecuteRules();
            setData(importedData);
            setDataLoadComplete(true);
        } catch (_) {
            setDataLoadFailed(true);
        }
    };

    const onButtonClick = () => {
        setExpand(!expand);
        if(!expand) setButtonText("hide other rules");
        else setButtonText("expand other rules");
    };

    const modifyDownloadArray = (id, checked) => {
        let tempArray;
        if(checked) tempArray = [...rulesToDownload, id];
        else tempArray = [...rulesToDownload.filter(ruleId => ruleId !== id)];
        setRulesToDownload([...tempArray]);
    };

    const dataFilter = (inputData, isFavouriteValue) => {
        return inputData.filter(rule => rule.isFavourite === isFavouriteValue)
                 .sort((a, b) => a.percentage - b.percentage)
                 .map((rule, i) => {
                    return <ReportProgressSummary
                                result={rule.percentage}
                                id={rule.id}
                                title={rule.name} key={i}
                                modifyDownloadArray={modifyDownloadArray}
                            />
                    })
    };

    return (
        <div className="test">
            {
                dataLoadFailed &&
                <div>Rule load failed</div>
            }
            {
                !dataLoadComplete &&
                    <LoadingSpinnerTitle holdingTitle="Loading rule data..."/>
            }
            {
                dataLoadComplete &&
                <Fragment>
                    <PageTitle title="System Overview"/>
                    <LineAndButtonContainerStyled>
                        <ContainerStyled>
                            {
                                dataFilter(data, true)
                            }
                        </ContainerStyled>
                        <ContainerStyled>
                            <ButtonStyled
                                    disabled={!rulesToDownload.length}
                                    onClick={() => DownloadFundDataById(rulesToDownload)}>
                                download
                            </ButtonStyled>
                        </ContainerStyled>
                    </LineAndButtonContainerStyled>

                    <LineBreakStyled/>

                    <ButtonStyled onClick={() => onButtonClick()}>{buttonText}</ButtonStyled>
                    {
                        expand &&
                        <ContainerStyled>
                            {
                                dataFilter(data, false)
                            }
                        </ContainerStyled>
                    }
                </Fragment>
            }
        </div>
    )
};

export default Summary;

DataMethod.js:

export function DownloadFundDataById(downloadArray) {
    downloadArray.forEach(id => window.open(baseApiUrl + '/xxxx/xxxx/' + id));
}

I can confirm the url is fine, just replaced for now

TestSetup:

const doc = jsdom.jsdom('<!doctype html><html><body></body></html>')

global.document = doc;

global.window = doc.defaultView;

configure({ adapter: new Adapter() });

global.expect = expect;
global.sandbox = sinon.createSandbox();
global.React = React;
global.mount = mount;
global.shallow = shallow;
global.render = render;
global.fetch = jestFetchMock;
global.act = act;

chai.use(chaiAsPromised);
chai.use(sinonChai);
chai.use(chaiEnzyme());
chai.use(chaiJestDiff());

console.error = () => {};
console.warn = () => {};

Current test output says that global.open is not being called. I know this makes sense as it isn't actually assigned as a prop to the onClick of the button or anything. This I think is one of my issues - I can't assign a stub to the button directly, but I'm trying not to re-write my code to fit my tests...

Managed to get this working with a couple of updates to my test file:

it(`should create correct download array when some rules are selected`, async () => {
        global.open = sandbox.stub(window, "open");

        fetch.mockResponseOnce(JSON.stringify(selectedRules));

        wrapper = mount(<Summary/>);
        await act(async () => {} );
        wrapper.update();

        wrapper.find('ReportProgressSummary').first().find('input').last().simulate('change', {target: {checked: true}});

        wrapper.find('button').first().simulate('click');
        expect(global.open).to.have.been.called;

    });

the sandbox.spy() was updated to a sandbox.stub() with (window, "open")

thanks to this article for the help!
https://github.com/mrdulin/mocha-chai-sinon-codelab/blob/master/src/stackoverflow/53524524/index.spec.js

Also the expect statement using to.be.called() is actually not a function and so was updated to to.be.called

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM