Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sweet-hairs-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': major
---

FIX: Add a new e2e test to verify QRL behavior without uncaught promises. Introduce retry logic for QRL resolution to handle potential promise retries, ensuring robustness in asynchronous operations.
6 changes: 4 additions & 2 deletions packages/qwik/src/core/shared/qrl/qrl-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
type InvokeTuple,
} from '../../use/use-core';
import { getQFuncs, QInstanceAttr } from '../utils/markers';
import { isPromise, maybeThen } from '../utils/promises';
import { isPromise, maybeThen, retryOnPromise } from '../utils/promises';
import { qDev, qSerialize, qTest, seal } from '../utils/qdev';
import { isArray, isFunction, type ValueOrPromise } from '../utils/types';
import type { QRLDev } from './qrl';
Expand Down Expand Up @@ -93,11 +93,13 @@ export const createQRL = <TYPE>(
// Note that we bind the current `this`
const bound = (...args: QrlArgs<TYPE>): ValueOrPromise<QrlReturn<TYPE> | undefined> => {
if (!qrl.resolved) {
return qrl.resolve().then((fn) => {
//@ts-ignore
return retryOnPromise(() => qrl.resolve()).then((fn) => {
if (!isFunction(fn)) {
throw qError(QError.qrlIsNotFunction);
}
return bound(...args);
// @ts-ignore
});
}
if (beforeFn && beforeFn() === false) {
Expand Down
32 changes: 32 additions & 0 deletions starters/apps/e2e/src/components/qrl/qrl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { $, component$, useComputed$, useSignal } from "@qwik.dev/core";

export const QRL = component$(() => {
return (
<>
<ShouldResolveInnerComputedQRL />
</>
);
});

export const ShouldResolveInnerComputedQRL = component$(() => {
const test = useComputed$(() => 0);
return <InnerComputedButton test={test} />;
});

export const InnerComputedButton = component$<any>((props) => {
const syncSelectionCounter = useSignal(0);
const syncSelection = $(() => {
syncSelectionCounter.value++;
props.test.value;
});

const handleClick = $(() => {
syncSelection();
});

return (
<button id="inner-computed-button" onClick$={handleClick}>
Click Me {syncSelectionCounter.value}
</button>
);
});
2 changes: 2 additions & 0 deletions starters/apps/e2e/src/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { UseId } from "./components/useid/useid";
import { Watch } from "./components/watch/watch";

import "./global.css";
import { QRL } from "./components/qrl/qrl";

const tests: Record<string, FunctionComponent> = {
"/e2e/two-listeners": () => <TwoListeners />,
Expand Down Expand Up @@ -71,6 +72,7 @@ const tests: Record<string, FunctionComponent> = {
"/e2e/build-variables": () => <BuildVariables />,
"/e2e/exception/render": () => <RenderExceptions />,
"/e2e/exception/use-task": () => <UseTaskExceptions />,
"/e2e/qrl": () => <QRL />,
};

export const Root = component$<{ pathname: string }>(({ pathname }) => {
Expand Down
14 changes: 14 additions & 0 deletions starters/e2e/e2e.qrl.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { expect, test } from "@playwright/test";
test("should update counter without uncaught promises", async ({ page }) => {
await page.goto("/e2e/qrl");
page.on("pageerror", (err) => expect(err).toEqual(undefined));
page.on("console", (msg) => {
if (msg.type() === "error") {
expect(msg.text()).toEqual(undefined);
}
});
const button = page.locator("#inner-computed-button");
await expect(button).toContainText("Click Me 0");

await button.click();
});