palerdot / svelte-speedometer

Svelte component for showing speedometer like gauge using d3
https://palerdot.in/svelte-speedometer/
16 stars 3 forks source link

TypeError: Cannot read property 'parentNode' of undefined #36

Closed JojoDz closed 2 years ago

JojoDz commented 2 years ago

We're using the speedometer in our app and great thanks for it.

We are unit testing with jest, and when we include the Speedometer in one svelte component, we have the following error :

TypeError: Cannot read property 'parentNode' of undefined 

When we follow the stack it leads to mountGauge :

config = getConfig({
                        PROPS,
                        parentWidth: gaugeDiv.parentNode.clientWidth,
                        parentHeight: gaugeDiv.parentNode.clientHeight
                });

So gaugeDiv is undefined. We've seen the unit tests of svelte-speedometer, and we don't understand why it fails with our tests.

Do you have an idea why gaugeDiv would not be bound ?

As @palerdot suggested, we rendered the speedometer inside a div, but the test still fails.

This is an extract of the Svelte file where we added Speedometer :

<section class='account'>
    <h1>{$_('account.home.title')}</h1>
    {#await userPromise}
        Loading user...
    {:then me}
        <div class='account__user'>
            <p class='account__user__username'><span>{me.firstname}</span> <span>{me.lastname}</span></p>
            <p class='account__user__email'>
                { $_('account.home.your_email') } <span>{me.email}</span>
            </p>
        </div>
               {#await getUserQuota(me.email)}
            Loading usage...
        {:then quota}
            <div class='account__subscription'>
                { $_('account.home.your_subscription') }
                <span>
                    {$number(price, { style: 'currency', currency })} / {   $_('signup.month')}
                    {$_('signup.for')} <span>{humanSize(quota.total, $_('account.home.sizes'))}</span>
                </span>
            </div>
            <div class='account__quota'>
                { $_('account.home.quota') }
                <span>{humanSize(quota.used, $_('account.home.sizes'))}/{humanSize(quota.total, $_('account.home.sizes'))}
                    ({$number(quota.used/quota.total, { style: 'percent' })})</span>
            </div>
            **<Speedometer
                height={180}
                maxValue={100}
                value={100*quota.used/quota.total}
                currentValueText={$_('account.volume.title', { values: { userVolumeStorage: $number(quota.used/quota.total, { style: 'percent' }) }})}
                needleColor="#f2ebe3"
                segments={3}
                customSegmentStops={[0, 75, 90, 100]}
                segmentColors={["#00D692", "#FFE032", "#FF504D"]}
            />**
        {/await}
    {:catch error}
        <p style="color: red">{error.message}</p>
    {/await}
</section>

<section class="account__unsubscribe">
    <div class="account__unsubscribe__content">
        <h1>{$_('account.unsubscribe.title')}</h1>
        <p>{$_('account.unsubscribe.content')}</p>
        <ConfirmButton 
            popupLabel={$_('account.unsubscribe.popover.title')} 
            noLabel={$_('account.unsubscribe.popover.no')} 
            yesLabel={$_('account.unsubscribe.popover.yes')} 
            callback={removeAccount}
            disabled={disabled}
        >
                {$_('account.unsubscribe.button')}
        </ConfirmButton>
        {#if globalMessage}
            <p>
                <Alert type={alertType} content=    {globalMessage} callback={() => globalMessage   = null}/>
            </p>
        {/if}
    </div>
</section>

Here is the sample test code where we get the TypeError message :

test('click yes-option button in popper proceeds to unsubscription', async () => {
        const {getByText} = render(unsubscribe)
        const buttonYes = getByText('Yes')
        await fireEvent.click(getByText('Cancel mandate and remove user'))
        await waitFor(() => {
            expect(buttonYes).toBeInTheDocument()
        })
        await fireEvent.click(buttonYes)

        await waitFor(()=> {
            expect(getByText('Account removed')).toBeInTheDocument()
        })
        expect(getByText('Cancel mandate and remove user')).toBeDisabled()
    })

The complete error message :

Unsubscribe › click yes-option button in popper proceeds to unsubscription   

TypeError: Cannot read property 'parentNode' of undefined                    

  at mountGauge (node_modules/svelte-speedometer/dist/index.js:6621:30)      
  at node_modules/svelte-speedometer/dist/index.js:6643:7                    
  at run (node_modules/svelte/internal/index.js:22:12)
      at Array.map (<anonymous>)
  at node_modules/svelte/internal/index.js:1734:45
  at flush (node_modules/svelte/internal/index.js:1042:17)
  at update (node_modules/svelte/internal/index.js:1380:13)
  at node_modules/svelte/internal/index.js:1387:13

I guess the problem lies in the fact that we are using two components from different libraries on the same Svelte page and both having onMount() parts : Speedometer and our customized Confirm Button (with a popup from the lib PopperJs). But how can I be sure? Do you have any other ideas ?

palerdot commented 2 years ago

I don't see an html wrapping around Speedometer.

Please try

<div style="width: 300px; height: 300px"> -> add an enclosing div which is missing in your code sample
   <Speedometer />
</div>
JojoDz commented 2 years ago

I added the missing html wrapping (just above). With your suggestion, I added :

<div class="account__speedometer" style="width: 300px; height: 300px">
                <Speedometer
                    height={180}
                    maxValue={100}
                    value={100*quota.used/quota.total}
                    currentValueText={$_('account.volume.title', { values: { userVolumeStorage: $number(quota.used/quota.total, { style: 'percent' }) }})}
                    needleColor="#f2ebe3"
                    segments={3}
                    customSegmentStops={[0, 75, 90, 100]}
                    segmentColors={["#00D692", "#FFE032", "#FF504D"]}
                />
            </div>

But still get the same error message ...

JojoDz commented 2 years ago

After deeper analysis, we found that the problem might come from bind:this. We were helped by this issue. It occurs both with gaugeDiv and d3_ref.

We managed to get a green test by adding a delay in mountGauge and updateGauge. So it seems related to bootstrap workflow of Svelte components. Also the test works in Webstorm.

palerdot commented 2 years ago

Glad things are working for you. I'm closing this issue as it is not related to the library.