@@ -165,6 +165,69 @@ test('renders in react mode with css modules', async ({ page }) => {
165165 await expectPreviewHasRenderedContent ( page )
166166} )
167167
168+ test ( 'reactJsx tag interpolation renders memo and forwardRef components' , async ( {
169+ page,
170+ } ) => {
171+ await waitForInitialRender ( page )
172+ await ensurePanelToolsVisible ( page , 'component' )
173+
174+ await page . getByRole ( 'button' , { name : 'Open tab App.tsx' } ) . click ( )
175+ await page . getByRole ( 'combobox' , { name : 'Render mode' } ) . selectOption ( 'react' )
176+
177+ await setComponentEditorSource (
178+ page ,
179+ [
180+ "import { memo, forwardRef } from 'react'" ,
181+ "import { reactJsx } from '@knighted/jsx/react'" ,
182+ '' ,
183+ 'type ButtonProps = {' ,
184+ ' label: string' ,
185+ '}' ,
186+ '' ,
187+ 'const MemoButton = memo(({ label }: ButtonProps) => (' ,
188+ ' <button type="button" data-testid="memo-button">' ,
189+ ' {label}' ,
190+ ' </button>' ,
191+ '))' ,
192+ '' ,
193+ 'const ForwardRefButton = forwardRef<HTMLButtonElement, ButtonProps>(' ,
194+ ' ({ label }, ref) => (' ,
195+ ' <button ref={ref} type="button" data-testid="forward-ref-button">' ,
196+ ' {label}' ,
197+ ' </button>' ,
198+ ' ),' ,
199+ ')' ,
200+ '' ,
201+ 'const App = () =>' ,
202+ ' reactJsx`' ,
203+ ' <section>' ,
204+ ' <${MemoButton} label="Memo OK" />' ,
205+ ' <${ForwardRefButton} label="ForwardRef OK" />' ,
206+ ' </section>' ,
207+ ' `' ,
208+ ] . join ( '\n' ) ,
209+ )
210+
211+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
212+ await expect ( getPreviewFrame ( page ) . getByTestId ( 'memo-button' ) ) . toHaveText ( 'Memo OK' )
213+ await expect ( getPreviewFrame ( page ) . getByTestId ( 'forward-ref-button' ) ) . toHaveText (
214+ 'ForwardRef OK' ,
215+ )
216+
217+ await expect
218+ . poll ( async ( ) => {
219+ return getPreviewFrame ( page )
220+ . locator ( 'html' )
221+ . evaluate (
222+ ( ) =>
223+ Array . from ( document . querySelectorAll ( '*' ) ) . filter ( node =>
224+ / ^ _ _ k x _ e x p r _ _ / i. test ( node . localName ) ,
225+ ) . length ,
226+ )
227+ } )
228+ . toBe ( 0 )
229+ } )
230+
168231test ( 'react mode keeps App.ts entry but surfaces rename guidance until compatible' , async ( {
169232 page,
170233} ) => {
@@ -306,6 +369,128 @@ test('css module imports expose class map for module tabs', async ({ page }) =>
306369 . not . toContain ( '.item:active' )
307370} )
308371
372+ test ( 'workspace modules support ?knighted-css&combined imports' , async ( { page } ) => {
373+ await waitForInitialRender ( page )
374+
375+ await ensurePanelToolsVisible ( page , 'component' )
376+ await ensurePanelToolsVisible ( page , 'styles' )
377+
378+ await page . getByRole ( 'button' , { name : 'Open tab App.tsx' } ) . click ( )
379+ await page . getByRole ( 'combobox' , { name : 'Render mode' } ) . selectOption ( 'react' )
380+
381+ await renameWorkspaceTab ( page , {
382+ from : 'app.css' ,
383+ to : 'button.module.css' ,
384+ } )
385+
386+ await page . getByRole ( 'button' , { name : 'Open tab button.module.css' } ) . click ( )
387+ await page . getByRole ( 'combobox' , { name : 'Style mode' } ) . selectOption ( 'module' )
388+
389+ await setWorkspaceTabSource ( page , {
390+ fileName : 'button.module.css' ,
391+ kind : 'styles' ,
392+ source : [ '.btn {' , ' color: rgb(7, 89, 160);' , ' font-weight: 700;' , '}' ] . join (
393+ '\n' ,
394+ ) ,
395+ } )
396+
397+ await addWorkspaceTab ( page , { type : 'script' } )
398+ await renameWorkspaceTab ( page , {
399+ from : 'module.tsx' ,
400+ to : 'button.tsx' ,
401+ } )
402+
403+ await setWorkspaceTabSource ( page , {
404+ fileName : 'button.tsx' ,
405+ source : [
406+ "import styles from '../styles/button.module.css'" ,
407+ '' ,
408+ 'type ButtonProps = {' ,
409+ ' label: string' ,
410+ '}' ,
411+ '' ,
412+ 'export const ReactButton = ({ label }: ButtonProps) => (' ,
413+ ' <button type="button" className={styles.btn}>{label}</button>' ,
414+ ')' ,
415+ ] . join ( '\n' ) ,
416+ } )
417+
418+ await setComponentEditorSource (
419+ page ,
420+ [
421+ "import { ReactButton, knightedCss } from './button.tsx?knighted-css&combined'" ,
422+ '' ,
423+ 'const App = () => (' ,
424+ ' <>' ,
425+ ' <style>{knightedCss}</style>' ,
426+ ' <ReactButton label="Combined query works" />' ,
427+ ' </>' ,
428+ ')' ,
429+ ] . join ( '\n' ) ,
430+ )
431+
432+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
433+ await expect ( page . locator ( '#preview-host pre.preview-runtime-error' ) ) . toHaveCount ( 0 )
434+ await expect (
435+ getPreviewFrame ( page ) . getByRole ( 'button' , { name : 'Combined query works' } ) ,
436+ ) . toHaveCSS ( 'color' , 'rgb(7, 89, 160)' )
437+ } )
438+
439+ test ( 'workspace style tabs support ?knighted-css query exports' , async ( { page } ) => {
440+ await waitForInitialRender ( page )
441+
442+ await ensurePanelToolsVisible ( page , 'component' )
443+ await ensurePanelToolsVisible ( page , 'styles' )
444+
445+ await page . getByRole ( 'button' , { name : 'Open tab app.css' } ) . click ( )
446+ await renameWorkspaceTab ( page , {
447+ from : 'app.css' ,
448+ to : 'button.module.css' ,
449+ } )
450+ await page . getByRole ( 'combobox' , { name : 'Style mode' } ) . selectOption ( 'module' )
451+
452+ await setWorkspaceTabSource ( page , {
453+ fileName : 'button.module.css' ,
454+ kind : 'styles' ,
455+ source : [
456+ '.btn {' ,
457+ ' color: rgb(14, 110, 173);' ,
458+ ' border: 1px solid rgb(14, 110, 173);' ,
459+ '}' ,
460+ ] . join ( '\n' ) ,
461+ } )
462+
463+ await page . getByRole ( 'button' , { name : 'Open tab App.tsx' } ) . click ( )
464+ await setComponentEditorSource (
465+ page ,
466+ [
467+ "import styles, { knightedCss } from '../styles/button.module.css?knighted-css'" ,
468+ '' ,
469+ 'const App = () => (' ,
470+ ' <>' ,
471+ ' <style>{knightedCss}</style>' ,
472+ ' <button' ,
473+ ' type="button"' ,
474+ ' className={styles.btn}' ,
475+ ' data-css-length={String(knightedCss.length)}' ,
476+ ' >' ,
477+ ' Style query works' ,
478+ ' </button>' ,
479+ ' </>' ,
480+ ')' ,
481+ ] . join ( '\n' ) ,
482+ )
483+
484+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
485+ await expect ( page . locator ( '#preview-host pre.preview-runtime-error' ) ) . toHaveCount ( 0 )
486+
487+ const previewButton = getPreviewFrame ( page ) . getByRole ( 'button' , {
488+ name : 'Style query works' ,
489+ } )
490+ await expect ( previewButton ) . toHaveCSS ( 'color' , 'rgb(14, 110, 173)' )
491+ await expect ( previewButton ) . toHaveAttribute ( 'data-css-length' , / [ 1 - 9 ] \d * / )
492+ } )
493+
309494test ( 'preview styles require explicit import from entry graph' , async ( { page } ) => {
310495 await waitForInitialRender ( page )
311496
0 commit comments