Skip to content

Commit ef2cbcb

Browse files
refactor: address pr comments.
1 parent 85da837 commit ef2cbcb

2 files changed

Lines changed: 147 additions & 7 deletions

File tree

playwright/rendering-modes/core.spec.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,128 @@ test('preview iframe keeps one base and one user style node across rerenders', a
481481
.toContain('rgb(70, 80, 90)')
482482
})
483483

484+
test('config patch keeps preview style order stable around app head styles', async ({
485+
page,
486+
}) => {
487+
await waitForInitialRender(page)
488+
489+
await setWorkspaceTabSource(page, {
490+
fileName: 'app.css',
491+
kind: 'styles',
492+
source: ['.counter-button { color: rgb(80, 90, 100); }'].join('\n'),
493+
})
494+
495+
await setComponentEditorSource(
496+
page,
497+
[
498+
"import '../styles/app.css'",
499+
'',
500+
"const injected = document.getElementById('app-head-style')",
501+
'if (!(injected instanceof HTMLStyleElement)) {',
502+
" const style = document.createElement('style')",
503+
" style.id = 'app-head-style'",
504+
" style.textContent = '.counter-button { border-radius: 0px; }'",
505+
' document.head.append(style)',
506+
'}',
507+
'',
508+
'const App = () => <button class="counter-button">order check</button>',
509+
'',
510+
].join('\n'),
511+
)
512+
513+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
514+
515+
await expect
516+
.poll(async () => {
517+
return getPreviewFrame(page)
518+
.locator('html')
519+
.evaluate(() => {
520+
const baseStyleElement = document.getElementById('knighted-preview-base-styles')
521+
const userStyleElement = document.getElementById('knighted-preview-user-styles')
522+
const appHeadStyleElement = document.getElementById('app-head-style')
523+
const styleElements = Array.from(document.head.querySelectorAll('style'))
524+
525+
if (
526+
!(baseStyleElement instanceof HTMLStyleElement) ||
527+
!(userStyleElement instanceof HTMLStyleElement) ||
528+
!(appHeadStyleElement instanceof HTMLStyleElement)
529+
) {
530+
return null
531+
}
532+
533+
return {
534+
baseIndex: styleElements.indexOf(baseStyleElement),
535+
userIndex: styleElements.indexOf(userStyleElement),
536+
appIndex: styleElements.indexOf(appHeadStyleElement),
537+
}
538+
})
539+
})
540+
.not.toBeNull()
541+
542+
const resolvedOrderBeforePatch = await getPreviewFrame(page)
543+
.locator('html')
544+
.evaluate(() => {
545+
const baseStyleElement = document.getElementById('knighted-preview-base-styles')
546+
const userStyleElement = document.getElementById('knighted-preview-user-styles')
547+
const appHeadStyleElement = document.getElementById('app-head-style')
548+
const styleElements = Array.from(document.head.querySelectorAll('style'))
549+
550+
if (
551+
!(baseStyleElement instanceof HTMLStyleElement) ||
552+
!(userStyleElement instanceof HTMLStyleElement) ||
553+
!(appHeadStyleElement instanceof HTMLStyleElement)
554+
) {
555+
return null
556+
}
557+
558+
return {
559+
baseIndex: styleElements.indexOf(baseStyleElement),
560+
userIndex: styleElements.indexOf(userStyleElement),
561+
appIndex: styleElements.indexOf(appHeadStyleElement),
562+
}
563+
})
564+
565+
if (!resolvedOrderBeforePatch) {
566+
throw new Error('Expected app-injected head style to exist before config patch.')
567+
}
568+
569+
expect(resolvedOrderBeforePatch.baseIndex).toBeLessThan(
570+
resolvedOrderBeforePatch.userIndex,
571+
)
572+
expect(resolvedOrderBeforePatch.userIndex).toBeLessThan(
573+
resolvedOrderBeforePatch.appIndex,
574+
)
575+
576+
await page.getByLabel('Background').fill('#456789')
577+
578+
await expect
579+
.poll(async () => {
580+
return getPreviewFrame(page)
581+
.locator('html')
582+
.evaluate(() => {
583+
const baseStyleElement = document.getElementById('knighted-preview-base-styles')
584+
const userStyleElement = document.getElementById('knighted-preview-user-styles')
585+
const appHeadStyleElement = document.getElementById('app-head-style')
586+
const styleElements = Array.from(document.head.querySelectorAll('style'))
587+
588+
if (
589+
!(baseStyleElement instanceof HTMLStyleElement) ||
590+
!(userStyleElement instanceof HTMLStyleElement) ||
591+
!(appHeadStyleElement instanceof HTMLStyleElement)
592+
) {
593+
return null
594+
}
595+
596+
return {
597+
baseIndex: styleElements.indexOf(baseStyleElement),
598+
userIndex: styleElements.indexOf(userStyleElement),
599+
appIndex: styleElements.indexOf(appHeadStyleElement),
600+
}
601+
})
602+
})
603+
.toEqual(resolvedOrderBeforePatch)
604+
})
605+
484606
test('nested module imports can bring styles into preview graph', async ({ page }) => {
485607
await waitForInitialRender(page)
486608

src/modules/preview-runtime/iframe-preview-executor.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,31 @@ const createIframeShellDocument = ({ channelId, parentOrigin, importMap }) => {
163163
}
164164
}
165165
166-
const userStyleElements = desiredUserStyleElementIds.map(styleElementId => {
166+
const userStyleElements = []
167+
let previousPreviewStyleElement = baseStyleElement
168+
169+
for (const styleElementId of desiredUserStyleElementIds) {
167170
let userStyleElement = document.getElementById(styleElementId)
168171
if (!(userStyleElement instanceof HTMLStyleElement)) {
169172
userStyleElement = document.createElement('style')
170173
userStyleElement.id = styleElementId
171-
document.head.append(userStyleElement)
174+
175+
if (
176+
previousPreviewStyleElement instanceof HTMLStyleElement &&
177+
previousPreviewStyleElement.parentNode === document.head
178+
) {
179+
document.head.insertBefore(
180+
userStyleElement,
181+
previousPreviewStyleElement.nextSibling,
182+
)
183+
} else {
184+
document.head.append(userStyleElement)
185+
}
172186
}
173187
174-
return userStyleElement
175-
})
188+
userStyleElements.push(userStyleElement)
189+
previousPreviewStyleElement = userStyleElement
190+
}
176191
177192
const firstUserStyleElement = userStyleElements[0]
178193
const isBaseAfterUser =
@@ -193,7 +208,6 @@ const createIframeShellDocument = ({ channelId, parentOrigin, importMap }) => {
193208
userStyleElement.textContent = String(
194209
__knightedState.visualConfig.userStyleSheets[index] ?? '',
195210
)
196-
document.head.append(userStyleElement)
197211
}
198212
199213
if (__knightedState.visualConfig.hostPadding.trim().length > 0) {
@@ -658,14 +672,18 @@ export const createWorkspaceIframePreviewBridge = ({
658672
timer,
659673
}
660674

675+
const stylePayload =
676+
Array.isArray(userStyleSheets) && userStyleSheets.length > 0
677+
? { userStyleSheets }
678+
: { cssText }
679+
661680
const payload = {
662681
mode,
663682
entrySpecifier,
664683
entryDisplaySpecifier,
665684
entryExportName,
666685
runtimeSpecifiers,
667-
cssText,
668-
userStyleSheets,
686+
...stylePayload,
669687
hostPadding,
670688
backgroundColor,
671689
importMap,

0 commit comments

Comments
 (0)