diff --git a/packages/qwik/src/core/v2/shared/scheduler.ts b/packages/qwik/src/core/v2/shared/scheduler.ts index 4a12750df4a..3a9ed3d368b 100644 --- a/packages/qwik/src/core/v2/shared/scheduler.ts +++ b/packages/qwik/src/core/v2/shared/scheduler.ts @@ -298,6 +298,7 @@ export const createScheduler = ( // we need to process cleanup tasks for deleted nodes nextChore.$type$ !== ChoreType.CLEANUP_VISIBLE ) { + DEBUG && debugTrace('skip chore', nextChore, currentChore, choreQueue); continue; } const returnValue = executeChore(nextChore); @@ -521,12 +522,13 @@ function debugChoreToString(chore: Chore): string { [ChoreType.COMPONENT_SSR]: 'COMPONENT_SSR', [ChoreType.JOURNAL_FLUSH]: 'JOURNAL_FLUSH', [ChoreType.VISIBLE]: 'VISIBLE', + [ChoreType.CLEANUP_VISIBLE]: 'CLEANUP_VISIBLE', [ChoreType.WAIT_FOR_ALL]: 'WAIT_FOR_ALL', [ChoreType.WAIT_FOR_COMPONENTS]: 'WAIT_FOR_COMPONENTS', } as any )[chore.$type$] || 'UNKNOWN: ' + chore.$type$; const host = String(chore.$host$).replaceAll(/\n.*/gim, ''); - const qrlTarget = (chore.$target$ as QRLInternal).$symbol$; + const qrlTarget = (chore.$target$ as QRLInternal)?.$symbol$; return `Chore(${type} ${chore.$type$ === ChoreType.QRL_RESOLVE ? qrlTarget : host} ${chore.$idx$})`; } diff --git a/packages/qwik/src/core/v2/tests/use-signal.spec.tsx b/packages/qwik/src/core/v2/tests/use-signal.spec.tsx index 7819f6e8df4..e157bf58734 100644 --- a/packages/qwik/src/core/v2/tests/use-signal.spec.tsx +++ b/packages/qwik/src/core/v2/tests/use-signal.spec.tsx @@ -14,6 +14,8 @@ import { Slot } from '../../render/jsx/slot.public'; import type { Signal as SignalType } from '../../state/signal'; import { untrack } from '../../use/use-core'; import { useSignal } from '../../use/use-signal'; +import { vnode_getFirstChild, vnode_getProp, vnode_locate } from '../client/vnode'; +import { QSubscribers } from '../../util/markers'; const debug = false; //true; Error.stackTraceLimit = 100; @@ -262,6 +264,38 @@ describe.each([ ); }); + it("should don't add multiple the same subscribers", async () => { + const Child = component$(() => { + return <>; + }); + + const Cmp = component$(() => { + const counter = useSignal(0); + const cleanupCounter = useSignal(0); + + return ( + <> + + +
{cleanupCounter.value + ''}
+ + ); + }); + + const { container } = await render(, { debug }); + + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + + const signalVNode = vnode_getFirstChild( + vnode_locate(container.rootVNode, container.element.querySelector('pre')!) + )!; + const subscribers = vnode_getProp(signalVNode, QSubscribers, null); + expect(subscribers).toHaveLength(1); + }); + describe('derived', () => { it('should update value directly in DOM', async () => { const log: string[] = []; diff --git a/packages/qwik/src/core/v2/tests/use-visible-task.spec.tsx b/packages/qwik/src/core/v2/tests/use-visible-task.spec.tsx index b5103d0f81b..acbf7caa683 100644 --- a/packages/qwik/src/core/v2/tests/use-visible-task.spec.tsx +++ b/packages/qwik/src/core/v2/tests/use-visible-task.spec.tsx @@ -11,6 +11,8 @@ import { useComputed$, useContextProvider, createContextId, + type Signal as SignalType, + useTask$, } from '@builder.io/qwik'; import { trigger, domRender, ssrRenderToDom } from '@builder.io/qwik/testing'; import { ErrorProvider } from '../../../testing/rendering.unit-util'; @@ -29,7 +31,7 @@ export function useDelay(value: string) { describe.each([ { render: ssrRenderToDom }, // - { render: domRender }, // + // { render: domRender }, // ])('$render.name: useVisibleTask', ({ render }) => { it('should execute visible task', async () => { const VisibleCmp = component$(() => { @@ -556,6 +558,49 @@ describe.each([ (globalThis as any).log = undefined; }); + it('should run cleanup with component rerender', async () => { + const Child = component$((props: { cleanupCounter: SignalType }) => { + useTask$(({ cleanup }) => { + cleanup(() => { + props.cleanupCounter.value++; + }); + }); + return ; + }); + + const Cmp = component$(() => { + const counter = useSignal(0); + const cleanupCounter = useSignal(0); + return ( +
+ + + {cleanupCounter.value + ''} +
+ ); + }); + + const { vNode, container } = await render(, { debug }); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + await trigger(container.element, 'button', 'click'); + + expect(vNode).toMatchVDOM( + +
+ + + + + {'6'} +
+
+ ); + }); + it('should handle promises and visible tasks', async () => { // vi.useFakeTimers(); const MyComp = component$(() => {