@@ -47,6 +47,90 @@ test('renders in react mode with css modules', async ({ page }) => {
4747 await expectPreviewHasRenderedContent ( page )
4848} )
4949
50+ test ( 'preview styles require explicit import from entry graph' , async ( { page } ) => {
51+ await waitForInitialRender ( page )
52+
53+ await setWorkspaceTabSource ( page , {
54+ fileName : 'app.css' ,
55+ kind : 'styles' ,
56+ source : [ '.counter-button { color: rgb(1, 2, 3); }' ] . join ( '\n' ) ,
57+ } )
58+
59+ await expect
60+ . poll ( async ( ) => {
61+ const styleContent = await getPreviewFrame ( page )
62+ . locator ( 'style' )
63+ . first ( )
64+ . textContent ( )
65+ return styleContent ?? ''
66+ } )
67+ . toContain ( 'rgb(1, 2, 3)' )
68+
69+ await setComponentEditorSource (
70+ page ,
71+ [
72+ 'const App = () => <button class="counter-button">No style import</button>' ,
73+ '' ,
74+ ] . join ( '\n' ) ,
75+ )
76+
77+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
78+ await expect
79+ . poll ( async ( ) => {
80+ const styleContent = await getPreviewFrame ( page )
81+ . locator ( 'style' )
82+ . first ( )
83+ . textContent ( )
84+ return styleContent ?? ''
85+ } )
86+ . not . toContain ( 'rgb(1, 2, 3)' )
87+ } )
88+
89+ test ( 'nested module imports can bring styles into preview graph' , async ( { page } ) => {
90+ await waitForInitialRender ( page )
91+
92+ await addWorkspaceTab ( page , { kind : 'component' } )
93+ await addWorkspaceTab ( page , { kind : 'styles' } )
94+
95+ await setWorkspaceTabSource ( page , {
96+ fileName : 'module.tsx' ,
97+ kind : 'component' ,
98+ source : [
99+ "import '../styles/module.css'" ,
100+ '' ,
101+ 'export const ModuleButton = () => <button class="module-button">Nested style</button>' ,
102+ ] . join ( '\n' ) ,
103+ } )
104+
105+ await setWorkspaceTabSource ( page , {
106+ fileName : 'module.css' ,
107+ kind : 'styles' ,
108+ source : [ '.module-button { color: rgb(9, 8, 7); }' ] . join ( '\n' ) ,
109+ } )
110+
111+ await setComponentEditorSource (
112+ page ,
113+ [
114+ "import { ModuleButton } from './module'" ,
115+ '' ,
116+ 'const App = () => <ModuleButton />' ,
117+ '' ,
118+ ] . join ( '\n' ) ,
119+ )
120+
121+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
122+ await expect ( getPreviewFrame ( page ) . getByRole ( 'button' ) ) . toContainText ( 'Nested style' )
123+ await expect
124+ . poll ( async ( ) => {
125+ const styleContent = await getPreviewFrame ( page )
126+ . locator ( 'style' )
127+ . first ( )
128+ . textContent ( )
129+ return styleContent ?? ''
130+ } )
131+ . toContain ( 'rgb(9, 8, 7)' )
132+ } )
133+
50134test ( 'transpiles TypeScript annotations in component source' , async ( { page } ) => {
51135 await waitForInitialRender ( page )
52136
@@ -641,6 +725,20 @@ test('persists render mode across reload', async ({ page }) => {
641725 await expect ( page . getByRole ( 'combobox' , { name : 'Render mode' } ) ) . toHaveValue ( 'react' )
642726} )
643727
728+ test ( 'persists style mode across reload' , async ( { page } ) => {
729+ await waitForInitialRender ( page )
730+
731+ await ensurePanelToolsVisible ( page , 'styles' )
732+ await page . getByRole ( 'combobox' , { name : 'Style mode' } ) . selectOption ( 'sass' )
733+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
734+
735+ await page . reload ( )
736+ await waitForInitialRender ( page )
737+ await ensurePanelToolsVisible ( page , 'styles' )
738+
739+ await expect ( page . getByRole ( 'combobox' , { name : 'Style mode' } ) ) . toHaveValue ( 'sass' )
740+ } )
741+
644742test ( 'renders with less style mode' , async ( { page } ) => {
645743 await waitForInitialRender ( page )
646744
@@ -827,6 +925,97 @@ test('workspace graph errors for missing modules remain deterministic', async ({
827925 )
828926} )
829927
928+ test ( 'renaming an imported module tab re-renders and surfaces missing import errors' , async ( {
929+ page,
930+ } ) => {
931+ await waitForInitialRender ( page )
932+
933+ await ensurePanelToolsVisible ( page , 'component' )
934+
935+ await addWorkspaceTab ( page )
936+ await setWorkspaceTabSource ( page , {
937+ fileName : 'module.tsx' ,
938+ source : [
939+ 'export const ItemWrap = ({ children }: { children: string }) => {' ,
940+ ' return <span>{children}</span>' ,
941+ '}' ,
942+ ] . join ( '\n' ) ,
943+ } )
944+
945+ await setWorkspaceTabSource ( page , {
946+ fileName : 'App.tsx' ,
947+ source : [
948+ "import { ItemWrap } from './module'" ,
949+ 'export const App = () => <ItemWrap>hello</ItemWrap>' ,
950+ ] . join ( '\n' ) ,
951+ } )
952+
953+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
954+
955+ await page . getByRole ( 'button' , { name : 'Rename tab module.tsx' } ) . click ( )
956+ const renameInput = page . getByLabel ( 'Rename module.tsx' )
957+ await renameInput . fill ( 'module-renamed.tsx' )
958+ await renameInput . press ( 'Enter' )
959+
960+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Error' )
961+ await expect ( page . locator ( '#preview-host pre' ) ) . toContainText (
962+ 'Preview entry references missing workspace module: ./module' ,
963+ )
964+ } )
965+
966+ test ( 'renaming default styles tab updates graph resolution and surfaces stale import' , async ( {
967+ page,
968+ } ) => {
969+ await waitForInitialRender ( page )
970+
971+ await ensurePanelToolsVisible ( page , 'styles' )
972+
973+ await page . getByRole ( 'button' , { name : 'Rename tab app.css' } ) . click ( )
974+ const renameInput = page . getByLabel ( 'Rename app.css' )
975+ await renameInput . fill ( 'app.less' )
976+ await renameInput . press ( 'Enter' )
977+
978+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Error' )
979+ await expect ( page . locator ( '#preview-host pre' ) ) . toContainText (
980+ 'Preview entry references missing workspace module: ../styles/app.css' ,
981+ )
982+
983+ await setWorkspaceTabSource ( page , {
984+ fileName : 'App.tsx' ,
985+ source : [
986+ "import '../styles/app.less'" ,
987+ '' ,
988+ 'type CounterButtonProps = {' ,
989+ ' label: string' ,
990+ ' onClick: (event: MouseEvent) => void' ,
991+ '}' ,
992+ '' ,
993+ 'const CounterButton = ({ label, onClick }: CounterButtonProps) => (' ,
994+ ' <button class="counter-button" type="button" onClick={onClick}>' ,
995+ ' {label}' ,
996+ ' </button>' ,
997+ ')' ,
998+ '' ,
999+ 'const App = () => {' ,
1000+ ' let count = 0' ,
1001+ ' const handleClick = (event: MouseEvent) => {' ,
1002+ ' count += 1' ,
1003+ ' const button = event.currentTarget as HTMLButtonElement' ,
1004+ ' button.textContent = `Clicks: ${count}`' ,
1005+ " button.dataset.active = count % 2 === 0 ? 'false' : 'true'" ,
1006+ " button.classList.toggle('is-even', count % 2 === 0)" ,
1007+ ' }' ,
1008+ '' ,
1009+ " return <CounterButton label='Clicks: 0' onClick={handleClick} />" ,
1010+ '}' ,
1011+ '' ,
1012+ ] . join ( '\n' ) ,
1013+ } )
1014+
1015+ await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
1016+ await expect ( page . locator ( '#preview-host pre' ) ) . toHaveCount ( 0 )
1017+ } )
1018+
8301019test ( 'workspace graph errors for circular imports remain deterministic' , async ( {
8311020 page,
8321021} ) => {
@@ -902,6 +1091,8 @@ test('children runtime errors recover after module fix and mode switches', async
9021091 await expect ( page . locator ( '#preview-host pre' ) ) . toHaveCount ( 0 )
9031092 await expect ( getPreviewFrame ( page ) . getByText ( 'hello children' ) ) . toBeVisible ( )
9041093
1094+ await openWorkspaceTab ( page , 'App.tsx' )
1095+ await ensurePanelToolsVisible ( page , 'component' )
9051096 await page . getByRole ( 'combobox' , { name : 'Render mode' } ) . selectOption ( 'react' )
9061097 await expect ( page . getByRole ( 'status' , { name : 'App status' } ) ) . toHaveText ( 'Rendered' )
9071098 await expect ( page . locator ( '#preview-host pre' ) ) . toHaveCount ( 0 )
0 commit comments