Skip to content

Commit 3a7577f

Browse files
committed
fix: correct event rendering and attaching
1 parent fb156d1 commit 3a7577f

File tree

6 files changed

+49
-40
lines changed

6 files changed

+49
-40
lines changed

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,10 @@ import { ChoreType } from '../shared/util-chore-type';
2626
import { escapeHTML } from '../shared/utils/character-escaping';
2727
import { _OWNER } from '../shared/utils/constants';
2828
import {
29-
getEventNameFromJsxEvent,
29+
getEventDataFromHtmlAttribute,
30+
getEventNameFromHtmlAttribute,
3031
getEventNameScopeFromJsxEvent,
3132
isHtmlAttributeAnEventName,
32-
isJsxPropertyAnEventName,
33-
jsxEventToHtmlAttribute,
3433
} from '../shared/utils/event-names';
3534
import { getFileLocationFromJsx } from '../shared/utils/jsx-filename';
3635
import {
@@ -604,23 +603,22 @@ export const vnode_diff = (
604603
// We never tell the vNode about them saving us time and memory.
605604
for (const key in constProps) {
606605
let value = constProps[key];
607-
if (isJsxPropertyAnEventName(key)) {
608-
// So for event handlers we must add them to the vNode so that qwikloader can look them up
609-
// But we need to mark them so that they don't get pulled into the diff.
610-
const eventName = getEventNameFromJsxEvent(key);
611-
const scope = getEventNameScopeFromJsxEvent(key);
612-
if (eventName) {
613-
vNewNode!.setProp(HANDLER_PREFIX + ':' + scope + ':' + eventName, value);
614-
registerQwikLoaderEvent(eventName);
615-
}
606+
if (isHtmlAttributeAnEventName(key)) {
607+
const data = getEventDataFromHtmlAttribute(key);
608+
if (data) {
609+
const scope = data[0];
610+
const eventName = data[1];
611+
612+
if (eventName) {
613+
vNewNode!.setProp(HANDLER_PREFIX + ':' + scope + ':' + eventName, value);
614+
registerQwikLoaderEvent(eventName);
615+
}
616616

617-
if (scope) {
618-
// add an event attr with empty value for qwikloader element selector.
619-
// We don't need value here. For ssr this value is a QRL,
620-
// but for CSR value should be just empty
621-
const htmlEvent = jsxEventToHtmlAttribute(key);
622-
if (htmlEvent) {
623-
vNewNode!.setAttr(htmlEvent, '', journal);
617+
if (scope) {
618+
// add an event attr with empty value for qwikloader element selector.
619+
// We don't need value here. For ssr this value is a QRL,
620+
// but for CSR value should be just empty
621+
vNewNode!.setAttr(key, '', journal);
624622
}
625623
}
626624

@@ -867,7 +865,7 @@ export const vnode_diff = (
867865
};
868866

869867
const recordJsxEvent = (key: string, value: any) => {
870-
const eventName = getEventNameFromJsxEvent(key);
868+
const eventName = getEventNameFromHtmlAttribute(key);
871869
const scope = getEventNameScopeFromJsxEvent(key);
872870
if (eventName) {
873871
record(':' + scope + ':' + eventName, value);
@@ -879,10 +877,7 @@ export const vnode_diff = (
879877
// add an event attr with empty value for qwikloader element selector.
880878
// We don't need value here. For ssr this value is a QRL,
881879
// but for CSR value should be just empty
882-
const htmlEvent = jsxEventToHtmlAttribute(key);
883-
if (htmlEvent) {
884-
record(htmlEvent, '');
885-
}
880+
record(key, '');
886881
}
887882
};
888883

@@ -910,7 +905,7 @@ export const vnode_diff = (
910905
} else if (dstKey === undefined) {
911906
// Destination exhausted: add remaining source keys
912907
const srcValue = srcAttrs[srcIdx + 1];
913-
if (isJsxPropertyAnEventName(srcKey)) {
908+
if (isHtmlAttributeAnEventName(srcKey)) {
914909
patchEventDispatch = true;
915910
recordJsxEvent(srcKey, srcValue);
916911
} else {
@@ -932,7 +927,7 @@ export const vnode_diff = (
932927
} else if (srcKey < dstKey) {
933928
// Source has a key not in destination: add it
934929
const srcValue = srcAttrs[srcIdx + 1];
935-
if (isJsxPropertyAnEventName(srcKey)) {
930+
if (isHtmlAttributeAnEventName(srcKey)) {
936931
patchEventDispatch = true;
937932
recordJsxEvent(srcKey, srcValue);
938933
} else {

packages/qwik/src/core/shared/component-execution.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { MAX_RETRY_ON_PROMISE_COUNT, isPromise, maybeThen, safeCall } from './ut
2929
import { isArray, isPrimitive, type ValueOrPromise } from './utils/types';
3030
import { getSubscriber } from '../reactive-primitives/subscriber';
3131
import { EffectProperty } from '../reactive-primitives/types';
32-
import { EventNameJSXScope } from './utils/event-names';
32+
import { EventNameHtmlScope } from './utils/event-names';
3333

3434
/**
3535
* Use `executeComponent` to execute a component.
@@ -160,8 +160,8 @@ function addUseOnEvents(
160160
// if the component is headless, we need to add the event to the placeholder element
161161
if (
162162
key === qVisibleEvent ||
163-
key.startsWith(EventNameJSXScope.document) ||
164-
key.startsWith(EventNameJSXScope.window)
163+
key.startsWith(EventNameHtmlScope.document) ||
164+
key.startsWith(EventNameHtmlScope.window)
165165
) {
166166
if (!placeholderElement) {
167167
const [createdElement, newJsx] = injectPlaceholderElement(jsxResult);

packages/qwik/src/core/shared/utils/event-names.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,15 @@ export function isPreventDefault(key: string): boolean {
209209
export const fromCamelToKebabCase = (text: string): string => {
210210
return text.replace(/([A-Z-])/g, '-$1').toLowerCase();
211211
};
212+
213+
export const getEventDataFromHtmlAttribute = (htmlKey: string): [string, string] | null => {
214+
const data = /^on(|-(window|document)):(.+)$/.exec(htmlKey);
215+
return data
216+
? [
217+
data[1]
218+
// remove `-` from prefix
219+
.substring(1),
220+
data[3],
221+
]
222+
: null;
223+
};

packages/qwik/src/core/ssr/ssr-render-jsx.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import type { QRL } from '../shared/qrl/qrl.public';
1616
import { qrlToString, type SerializationContext } from '../shared/serdes/index';
1717
import { DEBUG_TYPE, VirtualType } from '../shared/types';
1818
import { isAsyncGenerator } from '../shared/utils/async-generator';
19-
import { isHtmlAttributeAnEventName, isPreventDefault } from '../shared/utils/event-names';
19+
import {
20+
getEventDataFromHtmlAttribute,
21+
isHtmlAttributeAnEventName,
22+
isPreventDefault,
23+
} from '../shared/utils/event-names';
2024
import { EMPTY_ARRAY } from '../shared/utils/flyweight';
2125
import { getFileLocationFromJsx } from '../shared/utils/jsx-filename';
2226
import {
@@ -435,9 +439,9 @@ function addQwikEventToSerializationContext(
435439
qrl: QRL
436440
) {
437441
// TODO extract window/document too so qwikloader can precisely listen
438-
const match = /^on(|-(window|document)):(.+)$/.exec(key);
439-
if (match) {
440-
const eventName = match[3];
442+
const data = getEventDataFromHtmlAttribute(key);
443+
if (data) {
444+
const eventName = data[1];
441445
serializationCtx.$eventNames$.add(eventName);
442446
serializationCtx.$eventQrls$.add(qrl);
443447
}

packages/qwik/src/core/tests/use-visible-task.spec.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
useVisibleTask$,
1616
} from '@qwik.dev/core';
1717
import { domRender, ssrRenderToDom, trigger } from '@qwik.dev/core/testing';
18-
import { describe, expect, it, vi } from 'vitest';
18+
import { describe, expect, it } from 'vitest';
1919
import { ErrorProvider } from '../../testing/rendering.unit-util';
2020
import { delay } from '../shared/utils/promises';
2121

@@ -295,25 +295,24 @@ describe.each([
295295
});
296296

297297
it('should merge multiple visible tasks when empty', async () => {
298-
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
298+
(globalThis as any).log = [] as string[];
299299
const Cmp = component$(() => {
300300
useVisibleTask$(
301301
() => {
302-
console.warn('task1');
302+
(globalThis as any).log.push('task1');
303303
},
304304
{ strategy: 'document-ready' }
305305
);
306306
useVisibleTask$(() => {
307-
console.warn('task2');
307+
(globalThis as any).log.push('task2');
308308
});
309309
return <></>;
310310
});
311311
const { document } = await render(<Cmp />, { debug });
312312
if (render === ssrRenderToDom) {
313313
await trigger(document.body, 'script', ':document:qinit');
314314
}
315-
consoleSpy.mockRestore();
316-
expect(consoleSpy).toHaveBeenCalledTimes(3); // 2 tasks + warning about qvisible;
315+
expect((globalThis as any).log).toEqual(['task1', 'task2']);
317316
});
318317

319318
describe(render.name + ': track', () => {

packages/qwik/src/testing/element-fixture.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ export async function trigger(
125125
await dispatch(element, attrName, event, scope);
126126
}
127127
const waitForQueueChore = container?.$scheduler$(ChoreType.WAIT_FOR_QUEUE);
128-
await getTestPlatform().flush();
129128
if (waitForIdle && waitForQueueChore) {
130129
await waitForQueueChore.$returnValue$;
131130
}

0 commit comments

Comments
 (0)