From 02ccb50dba6b596dc8ade879d10bb7ef6f08f147 Mon Sep 17 00:00:00 2001 From: Varixo Date: Sun, 10 Nov 2024 15:19:15 +0100 Subject: [PATCH] mark event listeners inside loops as var props --- .../src/core/tests/use-lexical-scope.spec.tsx | 144 ++++++ .../qwik/src/core/tests/use-store.spec.tsx | 52 --- ...nent_with_event_listeners_inside_loop.snap | 419 ++++++++++++++++++ ..._test__example_functional_component_2.snap | 8 +- packages/qwik/src/optimizer/core/src/test.rs | 116 +++++ .../qwik/src/optimizer/core/src/transform.rs | 22 +- 6 files changed, 704 insertions(+), 57 deletions(-) create mode 100644 packages/qwik/src/core/tests/use-lexical-scope.spec.tsx create mode 100644 packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_component_with_event_listeners_inside_loop.snap diff --git a/packages/qwik/src/core/tests/use-lexical-scope.spec.tsx b/packages/qwik/src/core/tests/use-lexical-scope.spec.tsx new file mode 100644 index 00000000000..59fc37a7aa9 --- /dev/null +++ b/packages/qwik/src/core/tests/use-lexical-scope.spec.tsx @@ -0,0 +1,144 @@ +import { domRender, ssrRenderToDom, trigger } from '@qwik.dev/core/testing'; +import { describe, expect, it } from 'vitest'; +import { + component$, + useStore, + useSignal, + Fragment as Component, + Fragment as Signal, +} from '@qwik.dev/core'; + +const debug = false; //true; +Error.stackTraceLimit = 100; + +describe.each([ + { render: ssrRenderToDom }, // + { render: domRender }, // +])('$render.name: useLexicalScope', ({ render }) => { + it('should update const prop event value', async () => { + type Cart = string[]; + + const Parent = component$(() => { + const cart = useStore([]); + const results = useSignal(['foo']); + + return ( +
+ + + {results.value.map((item) => ( + + ))} +
    + {cart.map((item) => ( +
  • + {item} +
  • + ))} +
+
+ ); + }); + + const { vNode, document } = await render(, { debug }); + + expect(vNode).toMatchVDOM( + +
+ + +
    +
    +
    + ); + + await trigger(document.body, 'button#first', 'click'); + + expect(vNode).toMatchVDOM( + +
    + + +
      +
      +
      + ); + + await trigger(document.body, 'button#second', 'click'); + + expect(vNode).toMatchVDOM( + +
      + + +
        +
      • + item +
      • +
      +
      +
      + ); + }); + + describe('regression', () => { + it('#5662 - should update value in the list', async () => { + /** + * ROOT CAUSE ANALYSIS: This is a bug in Optimizer. The optimizer incorrectly marks the + * `onClick` listener as 'const'/'immutable'. Because it is const, the QRL associated with the + * click handler always points to the original object, and it is not updated. + */ + const Cmp = component$(() => { + const store = useStore<{ users: { name: string }[] }>({ users: [{ name: 'Giorgio' }] }); + + return ( +
      + {store.users.map((user, key) => ( + { + store.users = store.users.map(({ name }: { name: string }) => ({ + name: name === user.name ? name + '!' : name, + })); + }} + > + {user.name} + + ))} +
      + ); + }); + const { vNode, container } = await render(, { debug }); + expect(vNode).toMatchVDOM( + +
      + + {'Giorgio'} + +
      +
      + ); + await trigger(container.element, 'span', 'click'); + await trigger(container.element, 'span', 'click'); + await trigger(container.element, 'span', 'click'); + await trigger(container.element, 'span', 'click'); + await trigger(container.element, 'span', 'click'); + expect(vNode).toMatchVDOM( + +
      + + {'Giorgio!!!!!'} + +
      +
      + ); + }); + }); +}); diff --git a/packages/qwik/src/core/tests/use-store.spec.tsx b/packages/qwik/src/core/tests/use-store.spec.tsx index 6ec3f15722d..1de0d1726df 100644 --- a/packages/qwik/src/core/tests/use-store.spec.tsx +++ b/packages/qwik/src/core/tests/use-store.spec.tsx @@ -882,58 +882,6 @@ describe.each([ vi.useRealTimers(); }); - it.skip('#5662 - should update value in the list', async () => { - /** - * ROOT CAUSE ANALYSIS: This is a bug in Optimizer. The optimizer incorrectly marks the - * `onClick` listener as 'const'/'immutable'. Because it is const, the QRL associated with the - * click handler always points to the original object, and it is not updated. - */ - const Cmp = component$(() => { - const store = useStore<{ users: { name: string }[] }>({ users: [{ name: 'Giorgio' }] }); - - return ( -
      - {store.users.map((user, key) => ( - { - store.users = store.users.map(({ name }: { name: string }) => ({ - name: name === user.name ? name + '!' : name, - })); - }} - > - {user.name} - - ))} -
      - ); - }); - const { vNode, container } = await render(, { debug }); - expect(vNode).toMatchVDOM( - -
      - - {'Giorgio'} - -
      -
      - ); - await trigger(container.element, 'span', 'click'); - await trigger(container.element, 'span', 'click'); - await trigger(container.element, 'span', 'click'); - await trigger(container.element, 'span', 'click'); - await trigger(container.element, 'span', 'click'); - expect(vNode).toMatchVDOM( - -
      - - {'Giorgio!!!!!'} - -
      -
      - ); - }); - it('#5017 - should update child nodes for direct array', async () => { const Child = component$<{ columns: string }>(({ columns }) => { return
      Child: {columns}
      ; diff --git a/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_component_with_event_listeners_inside_loop.snap b/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_component_with_event_listeners_inside_loop.snap new file mode 100644 index 00000000000..aa5ad2910ac --- /dev/null +++ b/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_component_with_event_listeners_inside_loop.snap @@ -0,0 +1,419 @@ +--- +source: packages/qwik/src/optimizer/core/src/test.rs +assertion_line: 3850 +expression: output +--- +==INPUT== + + +import { $, component$, useStore, useSignal } from '@qwik.dev/core'; + +export const App = component$(() => { + const cart = useStore([]); + const results = useSignal(['foo']); + + function loopArrowFn(results: string[]) { + return results.map((item) => ( + { + cart.push(item); + }} + > + {item} + + )); + } + + function loopForI(results: string[]) { + const items = []; + for (let i = 0; i < results.length; i++) { + items.push( + { + cart.push(results[i]); + }} + > + {results[i]} + + ); + } + return items; + } + + function loopForOf(results: string[]) { + const items = []; + for (const item of results) { + items.push( + { + cart.push(item); + }} + > + {item} + + ); + } + return items; + } + + function loopForIn(results: string[]) { + const items = []; + for (const key in results) { + items.push( + { + cart.push(results[key]); + }} + > + {results[key]} + + ); + } + return items; + } + + function loopWhile(results: string[]) { + const items = []; + let i = 0; + while (i < results.length) { + items.push( + { + cart.push(results[i]); + }} + > + {results[i]} + + ); + i++; + } + return items; + } + + return ( +
      + {results.value.map((item) => ( + + ))} + {loopArrowFn(results.value)} + {loopForI(results.value)} + {loopForOf(results.value)} + {loopForIn(results.value)} + {loopWhile(results.value)} +
      + ); + }); + +============================= test.js == + +import { componentQrl } from "@qwik.dev/core"; +import { qrl } from "@qwik.dev/core"; +export const App = /*#__PURE__*/ componentQrl(/*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_ckEPmXZlub0"), "App_component_ckEPmXZlub0")); + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;AAGA,OAAO,MAAM,oBAAM,iHAqGZ\"}") +============================= test.tsx_App_component_loopWhile_span_onClick_ycCPdh6iazg.js (ENTRY POINT)== + +import { useLexicalScope } from "@qwik.dev/core"; +export const App_component_loopWhile_span_onClick_ycCPdh6iazg = ()=>{ + const [cart, i, results] = useLexicalScope(); + cart.push(results[i]); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";gEAyEwB;;IACR,KAAK,IAAI,CAAC,OAAO,CAAC,EAAE\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_loopWhile_span_onClick_ycCPdh6iazg", + "entry": null, + "displayName": "test.tsx_App_component_loopWhile_span_onClick", + "hash": "ycCPdh6iazg", + "canonicalFilename": "test.tsx_App_component_loopWhile_span_onClick_ycCPdh6iazg", + "path": "", + "extension": "js", + "parent": "App_component_ckEPmXZlub0", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [ + 1699, + 1761 + ] +} +*/ +============================= test.tsx_App_component_loopArrowFn_span_onClick_xyQUo7XwaEM.js (ENTRY POINT)== + +import { useLexicalScope } from "@qwik.dev/core"; +export const App_component_loopArrowFn_span_onClick_xyQUo7XwaEM = ()=>{ + const [cart, item] = useLexicalScope(); + cart.push(item); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";kEAUsB;;IACR,KAAK,IAAI,CAAC\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_loopArrowFn_span_onClick_xyQUo7XwaEM", + "entry": null, + "displayName": "test.tsx_App_component_loopArrowFn_span_onClick", + "hash": "xyQUo7XwaEM", + "canonicalFilename": "test.tsx_App_component_loopArrowFn_span_onClick_xyQUo7XwaEM", + "path": "", + "extension": "js", + "parent": "App_component_ckEPmXZlub0", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [ + 321, + 373 + ] +} +*/ +============================= test.tsx_App_component_loopForIn_span_onClick_1mDJD4xhUZ8.js (ENTRY POINT)== + +import { useLexicalScope } from "@qwik.dev/core"; +export const App_component_loopForIn_span_onClick_1mDJD4xhUZ8 = ()=>{ + const [cart, key, results] = useLexicalScope(); + cart.push(results[key]); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";gEAwDwB;;IACR,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_loopForIn_span_onClick_1mDJD4xhUZ8", + "entry": null, + "displayName": "test.tsx_App_component_loopForIn_span_onClick", + "hash": "1mDJD4xhUZ8", + "canonicalFilename": "test.tsx_App_component_loopForIn_span_onClick_1mDJD4xhUZ8", + "path": "", + "extension": "js", + "parent": "App_component_ckEPmXZlub0", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [ + 1324, + 1388 + ] +} +*/ +============================= test.tsx_App_component_loopForOf_span_onClick_AjlGKUbcCKY.js (ENTRY POINT)== + +import { useLexicalScope } from "@qwik.dev/core"; +export const App_component_loopForOf_span_onClick_AjlGKUbcCKY = ()=>{ + const [cart, item] = useLexicalScope(); + cart.push(item); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";gEAwCwB;;IACR,KAAK,IAAI,CAAC\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_loopForOf_span_onClick_AjlGKUbcCKY", + "entry": null, + "displayName": "test.tsx_App_component_loopForOf_span_onClick", + "hash": "AjlGKUbcCKY", + "canonicalFilename": "test.tsx_App_component_loopForOf_span_onClick_AjlGKUbcCKY", + "path": "", + "extension": "js", + "parent": "App_component_ckEPmXZlub0", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [ + 984, + 1040 + ] +} +*/ +============================= test.tsx_App_component_div_button_onClick_f5NwW9e63a4.js (ENTRY POINT)== + +import { useLexicalScope } from "@qwik.dev/core"; +export const App_component_div_button_onClick_f5NwW9e63a4 = ()=>{ + const [cart, item] = useLexicalScope(); + cart.push(item); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";4DA0FwB;;IACR,KAAK,IAAI,CAAC\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_div_button_onClick_f5NwW9e63a4", + "entry": null, + "displayName": "test.tsx_App_component_div_button_onClick", + "hash": "f5NwW9e63a4", + "canonicalFilename": "test.tsx_App_component_div_button_onClick_f5NwW9e63a4", + "path": "", + "extension": "js", + "parent": "App_component_ckEPmXZlub0", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [ + 2033, + 2089 + ] +} +*/ +============================= test.tsx_App_component_ckEPmXZlub0.js (ENTRY POINT)== + +import { _fnSignal } from "@qwik.dev/core"; +import { _jsxSorted } from "@qwik.dev/core"; +import { qrl } from "@qwik.dev/core"; +import { useSignal } from "@qwik.dev/core"; +import { useStore } from "@qwik.dev/core"; +export const App_component_ckEPmXZlub0 = ()=>{ + const cart = useStore([]); + const results = useSignal([ + 'foo' + ]); + function loopArrowFn(results) { + return results.map((item)=>/*#__PURE__*/ _jsxSorted("span", { + onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_loopArrowFn_span_onClick_xyQUo7XwaEM"), "App_component_loopArrowFn_span_onClick_xyQUo7XwaEM", [ + cart, + item + ]) + }, null, item, 3, "u6_0")); + } + function loopForI(results) { + const items = []; + for(let i = 0; i < results.length; i++)items.push(/*#__PURE__*/ _jsxSorted("span", { + onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_loopForI_span_onClick_MXjGbUTgkco"), "App_component_loopForI_span_onClick_MXjGbUTgkco", [ + cart, + i, + results + ]) + }, null, _fnSignal((p0, p1)=>p1[p0], [ + i, + results + ], "p1[p0]"), 3, "u6_1")); + return items; + } + function loopForOf(results) { + const items = []; + for (const item of results)items.push(/*#__PURE__*/ _jsxSorted("span", { + onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_loopForOf_span_onClick_AjlGKUbcCKY"), "App_component_loopForOf_span_onClick_AjlGKUbcCKY", [ + cart, + item + ]) + }, null, item, 3, "u6_2")); + return items; + } + function loopForIn(results) { + const items = []; + for(const key in results)items.push(/*#__PURE__*/ _jsxSorted("span", { + onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_loopForIn_span_onClick_1mDJD4xhUZ8"), "App_component_loopForIn_span_onClick_1mDJD4xhUZ8", [ + cart, + key, + results + ]) + }, null, _fnSignal((p0, p1)=>p1[p0], [ + key, + results + ], "p1[p0]"), 3, "u6_3")); + return items; + } + function loopWhile(results) { + const items = []; + let i = 0; + while(i < results.length){ + items.push(/*#__PURE__*/ _jsxSorted("span", { + onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_loopWhile_span_onClick_ycCPdh6iazg"), "App_component_loopWhile_span_onClick_ycCPdh6iazg", [ + cart, + i, + results + ]) + }, null, _fnSignal((p0, p1)=>p1[p0], [ + i, + results + ], "p1[p0]"), 3, "u6_4")); + i++; + } + return items; + } + return /*#__PURE__*/ _jsxSorted("div", null, null, [ + results.value.map((item)=>/*#__PURE__*/ _jsxSorted("button", { + onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_div_button_onClick_f5NwW9e63a4"), "App_component_div_button_onClick_f5NwW9e63a4", [ + cart, + item + ]) + }, { + id: "second" + }, item, 3, "u6_5")), + loopArrowFn(results.value), + loopForI(results.value), + loopForOf(results.value), + loopForIn(results.value), + loopWhile(results.value) + ], 1, "u6_6"); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;yCAG8B;IACxB,MAAM,OAAO,SAAmB,EAAE;IAClC,MAAM,UAAU,UAAU;QAAC;KAAM;IAEjC,SAAS,YAAY,OAAiB;QACpC,OAAO,QAAQ,GAAG,CAAC,CAAC,qBAClB,WAAC;gBACC,QAAQ;;;;qBAIP;IAGP;IAEA,SAAS,SAAS,OAAiB;QACjC,MAAM,QAAQ,EAAE;QAChB,IAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,MAAM,EAAE,IAClC,MAAM,IAAI,eACR,WAAC;YACC,QAAQ;;;;;qCAIP,EAAO,IAAG;;;;QAIjB,OAAO;IACT;IAEA,SAAS,UAAU,OAAiB;QAClC,MAAM,QAAQ,EAAE;QAChB,KAAK,MAAM,QAAQ,QACjB,MAAM,IAAI,eACR,WAAC;YACC,QAAQ;;;;iBAIP;QAIP,OAAO;IACT;IAEA,SAAS,UAAU,OAAiB;QAClC,MAAM,QAAQ,EAAE;QAChB,IAAK,MAAM,OAAO,QAChB,MAAM,IAAI,eACR,WAAC;YACC,QAAQ;;;;;qCAIP,EAAO,IAAK;;;;QAInB,OAAO;IACT;IAEA,SAAS,UAAU,OAAiB;QAClC,MAAM,QAAQ,EAAE;QAChB,IAAI,IAAI;QACR,MAAO,IAAI,QAAQ,MAAM,CAAE;YACzB,MAAM,IAAI,eACR,WAAC;gBACC,QAAQ;;;;;yCAIP,EAAO,IAAG;;;;YAGf;QACF;QACA,OAAO;IACT;IAEA,qBACE,WAAC;QACE,QAAQ,KAAK,CAAC,GAAG,CAAC,CAAC,qBAClB,WAAC;gBAEC,QAAQ;;;;;gBADR,IAAG;eAKF;QAGJ,YAAY,QAAQ,KAAK;QACzB,SAAS,QAAQ,KAAK;QACtB,UAAU,QAAQ,KAAK;QACvB,UAAU,QAAQ,KAAK;QACvB,UAAU,QAAQ,KAAK;;AAG9B\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_ckEPmXZlub0", + "entry": null, + "displayName": "test.tsx_App_component", + "hash": "ckEPmXZlub0", + "canonicalFilename": "test.tsx_App_component_ckEPmXZlub0", + "path": "", + "extension": "js", + "parent": null, + "ctxKind": "function", + "ctxName": "component$", + "captures": false, + "loc": [ + 102, + 2377 + ] +} +*/ +============================= test.tsx_App_component_loopForI_span_onClick_MXjGbUTgkco.js (ENTRY POINT)== + +import { useLexicalScope } from "@qwik.dev/core"; +export const App_component_loopForI_span_onClick_MXjGbUTgkco = ()=>{ + const [cart, i, results] = useLexicalScope(); + cart.push(results[i]); +}; + + +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";+DAwBwB;;IACR,KAAK,IAAI,CAAC,OAAO,CAAC,EAAE\"}") +/* +{ + "origin": "test.tsx", + "name": "App_component_loopForI_span_onClick_MXjGbUTgkco", + "entry": null, + "displayName": "test.tsx_App_component_loopForI_span_onClick", + "hash": "MXjGbUTgkco", + "canonicalFilename": "test.tsx_App_component_loopForI_span_onClick_MXjGbUTgkco", + "path": "", + "extension": "js", + "parent": "App_component_ckEPmXZlub0", + "ctxKind": "eventHandler", + "ctxName": "onClick$", + "captures": true, + "loc": [ + 631, + 693 + ] +} +*/ +== DIAGNOSTICS == + +[] diff --git a/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_functional_component_2.snap b/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_functional_component_2.snap index a58b19a2b0c..dcd3e21f667 100644 --- a/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_functional_component_2.snap +++ b/packages/qwik/src/optimizer/core/src/snapshots/qwik_core__test__example_functional_component_2.snap @@ -1,6 +1,6 @@ --- source: packages/qwik/src/optimizer/core/src/test.rs -assertion_line: 347 +assertion_line: 348 expression: output --- ==INPUT== @@ -131,19 +131,19 @@ export const App_component_ckEPmXZlub0 = (props)=>{ ]) }, [ /*#__PURE__*/ _jsxSorted("span", null, null, _wrapProp(state, "count"), 3, null), - buttons.map((btn)=>/*#__PURE__*/ _jsxSorted("button", null, { + buttons.map((btn)=>/*#__PURE__*/ _jsxSorted("button", { onClick$: /*#__PURE__*/ qrl(()=>import("./test.tsx_App_component_div_button_onClick_f5NwW9e63a4"), "App_component_div_button_onClick_f5NwW9e63a4", [ btn, props, state, thing ]) - }, _wrapProp(btn, "name"), 3, "u6_0")) + }, null, _wrapProp(btn, "name"), 3, "u6_0")) ], 0, "u6_1"); }; -Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;yCAQ8B,CAAC;IAC9B,MAAM,QAAQ;IACd,MAAM,QAAQ,SAAS;QAAC,OAAO;IAAC;IAGhC,MAAM,SAAS,MAAM,KAAK,GAAG;IAC7B,qBACC,WAAC;QAAI,QAAQ;;;;;sBACZ,WAAC,8BAAM;QACN,QAAQ,GAAG,CAAC,CAAA,oBACZ,WAAC;gBACA,QAAQ;;;;;;yBAEP;;AAON\"}") +Some("{\"version\":3,\"sources\":[\"/user/qwik/src/test.tsx\"],\"names\":[],\"mappings\":\";;;;;yCAQ8B,CAAC;IAC9B,MAAM,QAAQ;IACd,MAAM,QAAQ,SAAS;QAAC,OAAO;IAAC;IAGhC,MAAM,SAAS,MAAM,KAAK,GAAG;IAC7B,qBACC,WAAC;QAAI,QAAQ;;;;;sBACZ,WAAC,8BAAM;QACN,QAAQ,GAAG,CAAC,CAAA,oBACZ,WAAC;gBACA,QAAQ;;;;;;+BAEP;;AAON\"}") /* { "origin": "test.tsx", diff --git a/packages/qwik/src/optimizer/core/src/test.rs b/packages/qwik/src/optimizer/core/src/test.rs index f02a957c615..f493439d4df 100644 --- a/packages/qwik/src/optimizer/core/src/test.rs +++ b/packages/qwik/src/optimizer/core/src/test.rs @@ -3845,6 +3845,122 @@ fn rename_builder_io() { }); } +#[test] +fn example_component_with_event_listeners_inside_loop() { + test_input!(TestInput { + code: r#" +import { $, component$, useStore, useSignal } from '@qwik.dev/core'; + +export const App = component$(() => { + const cart = useStore([]); + const results = useSignal(['foo']); + + function loopArrowFn(results: string[]) { + return results.map((item) => ( + { + cart.push(item); + }} + > + {item} + + )); + } + + function loopForI(results: string[]) { + const items = []; + for (let i = 0; i < results.length; i++) { + items.push( + { + cart.push(results[i]); + }} + > + {results[i]} + + ); + } + return items; + } + + function loopForOf(results: string[]) { + const items = []; + for (const item of results) { + items.push( + { + cart.push(item); + }} + > + {item} + + ); + } + return items; + } + + function loopForIn(results: string[]) { + const items = []; + for (const key in results) { + items.push( + { + cart.push(results[key]); + }} + > + {results[key]} + + ); + } + return items; + } + + function loopWhile(results: string[]) { + const items = []; + let i = 0; + while (i < results.length) { + items.push( + { + cart.push(results[i]); + }} + > + {results[i]} + + ); + i++; + } + return items; + } + + return ( +
      + {results.value.map((item) => ( + + ))} + {loopArrowFn(results.value)} + {loopForI(results.value)} + {loopForOf(results.value)} + {loopForIn(results.value)} + {loopWhile(results.value)} +
      + ); + }); +"# + .to_string(), + transpile_ts: true, + transpile_jsx: true, + ..TestInput::default() + }); +} + // TODO(misko): Make this test work by implementing strict serialization. // #[test] // fn example_of_synchronous_qrl_that_cant_be_serialized() { diff --git a/packages/qwik/src/optimizer/core/src/transform.rs b/packages/qwik/src/optimizer/core/src/transform.rs index 4200aff1b44..f92c9097113 100644 --- a/packages/qwik/src/optimizer/core/src/transform.rs +++ b/packages/qwik/src/optimizer/core/src/transform.rs @@ -111,6 +111,7 @@ pub struct QwikTransform<'a> { file_hash: u64, jsx_key_counter: u32, root_jsx_mode: bool, + inside_loop: bool, } pub struct QwikTransformOptions<'a> { @@ -247,6 +248,7 @@ impl<'a> QwikTransform<'a> { immutable_function_cmp, root_jsx_mode: true, jsx_mutable: false, + inside_loop: false, options, } } @@ -1375,7 +1377,9 @@ impl<'a> QwikTransform<'a> { key: node.key.clone(), }), )); - if is_fn { + if self.inside_loop { + var_props.push(converted_prop.fold_with(self)); + } else if is_fn { if is_const { maybe_const_props .push(converted_prop.fold_with(self)); @@ -1811,6 +1815,9 @@ impl<'a> Fold for QwikTransform<'a> { let prev_jsx_mutable = self.jsx_mutable; self.jsx_mutable = false; + let prev_inside_loop = self.inside_loop; + self.inside_loop = self.stack_ctxt.last() != Some(&QCOMPONENT.to_string()); + let current_scope = self .decl_stack .last_mut() @@ -1829,6 +1836,7 @@ impl<'a> Fold for QwikTransform<'a> { let o = node.fold_children_with(self); self.root_jsx_mode = prev; self.jsx_mutable = prev_jsx_mutable; + self.inside_loop = prev_inside_loop; self.decl_stack.pop(); o @@ -1838,8 +1846,11 @@ impl<'a> Fold for QwikTransform<'a> { self.decl_stack.push(vec![]); let prev = self.root_jsx_mode; self.root_jsx_mode = true; + let prev_inside_loop = self.inside_loop; + self.inside_loop = true; let o = node.fold_children_with(self); self.root_jsx_mode = prev; + self.inside_loop = prev_inside_loop; self.decl_stack.pop(); o @@ -1849,8 +1860,11 @@ impl<'a> Fold for QwikTransform<'a> { self.decl_stack.push(vec![]); let prev = self.root_jsx_mode; self.root_jsx_mode = true; + let prev_inside_loop = self.inside_loop; + self.inside_loop = true; let o = node.fold_children_with(self); self.root_jsx_mode = prev; + self.inside_loop = prev_inside_loop; self.decl_stack.pop(); o @@ -1860,8 +1874,11 @@ impl<'a> Fold for QwikTransform<'a> { self.decl_stack.push(vec![]); let prev = self.root_jsx_mode; self.root_jsx_mode = true; + let prev_inside_loop = self.inside_loop; + self.inside_loop = true; let o = node.fold_children_with(self); self.root_jsx_mode = prev; + self.inside_loop = prev_inside_loop; self.decl_stack.pop(); o @@ -1909,8 +1926,11 @@ impl<'a> Fold for QwikTransform<'a> { self.decl_stack.push(vec![]); let prev = self.root_jsx_mode; self.root_jsx_mode = true; + let prev_inside_loop = self.inside_loop; + self.inside_loop = true; let o = node.fold_children_with(self); + self.inside_loop = prev_inside_loop; self.root_jsx_mode = prev; self.decl_stack.pop();