test(dom-css): share CSS fixture suite
This commit is contained in:
parent
95fedac2b7
commit
101a047e4d
|
|
@ -1,44 +1,96 @@
|
|||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { compileTailwindCSS } from '@open-pencil/dom-css'
|
||||
|
||||
import { tailwindButtonClasses, tailwindCardClasses } from '#tests/helpers/dom-css'
|
||||
|
||||
import { expect, test } from '../fixtures'
|
||||
|
||||
const STYLE_PROPERTIES = [
|
||||
async function setStyledContent(page: Page, css: string, body: string) {
|
||||
await page.setContent(`
|
||||
<style>${css}</style>
|
||||
${body}
|
||||
`)
|
||||
}
|
||||
|
||||
async function computedStyleProperties(
|
||||
page: Page,
|
||||
selector: string,
|
||||
properties: readonly string[]
|
||||
) {
|
||||
return page.locator(selector).evaluate((element, styleProperties) => {
|
||||
const computed = getComputedStyle(element)
|
||||
return Object.fromEntries(
|
||||
styleProperties.map((property) => [property, computed.getPropertyValue(property)])
|
||||
)
|
||||
}, properties)
|
||||
}
|
||||
|
||||
test.describe('@open-pencil/dom-css browser CSS runtime oracle', () => {
|
||||
test('resolves Tailwind card variables and calc values in a real browser', async ({ page }) => {
|
||||
const css = await compileTailwindCSS(tailwindCardClasses)
|
||||
await setStyledContent(
|
||||
page,
|
||||
css,
|
||||
`<article class="${tailwindCardClasses.join(' ')}"><h1>OpenPencil</h1></article>`
|
||||
)
|
||||
|
||||
const styles = await computedStyleProperties(page, 'article', [
|
||||
'background-color',
|
||||
'border-radius',
|
||||
'color',
|
||||
'display',
|
||||
'flex-direction',
|
||||
'gap',
|
||||
'height',
|
||||
'padding-top',
|
||||
'width'
|
||||
] as const
|
||||
|
||||
test.describe('@open-pencil/dom-css browser CSS runtime oracle', () => {
|
||||
test('resolves Tailwind variables and calc values in a real browser', async ({ page }) => {
|
||||
const classes = ['flex', 'gap-3', 'w-80', 'p-6', 'rounded-xl', 'bg-white', 'text-slate-900']
|
||||
const css = await compileTailwindCSS(classes)
|
||||
await page.setContent(`
|
||||
<style>${css}</style>
|
||||
<article class="${classes.join(' ')}">
|
||||
<h1>OpenPencil</h1>
|
||||
</article>
|
||||
`)
|
||||
|
||||
const styles = await page.locator('article').evaluate((element, properties) => {
|
||||
const computed = getComputedStyle(element)
|
||||
return Object.fromEntries(
|
||||
properties.map((property) => [property, computed.getPropertyValue(property)])
|
||||
)
|
||||
}, STYLE_PROPERTIES)
|
||||
])
|
||||
|
||||
expect(styles.display).toBe('flex')
|
||||
expect(styles['flex-direction']).toBe('column')
|
||||
expect(styles.gap).toBe('12px')
|
||||
expect(styles.width).toBe('320px')
|
||||
expect(styles.height).toBe('176px')
|
||||
expect(styles['padding-top']).toBe('24px')
|
||||
expect(styles['border-radius']).toBe('12px')
|
||||
expect(styles['background-color']).toBe('rgb(255, 255, 255)')
|
||||
expect(styles.color).toMatch(/^(oklch|rgb)\(/)
|
||||
})
|
||||
|
||||
test('resolves Tailwind button alignment, spacing, and typography', async ({ page }) => {
|
||||
const css = await compileTailwindCSS(tailwindButtonClasses)
|
||||
await setStyledContent(
|
||||
page,
|
||||
css,
|
||||
`<button class="${tailwindButtonClasses.join(' ')}">Create design</button>`
|
||||
)
|
||||
|
||||
const styles = await computedStyleProperties(page, 'button', [
|
||||
'align-items',
|
||||
'border-radius',
|
||||
'color',
|
||||
'display',
|
||||
'font-size',
|
||||
'font-weight',
|
||||
'gap',
|
||||
'justify-content',
|
||||
'padding-left',
|
||||
'padding-top'
|
||||
])
|
||||
|
||||
expect(styles.display).toBe('inline-flex')
|
||||
expect(styles['align-items']).toBe('center')
|
||||
expect(styles['justify-content']).toBe('center')
|
||||
expect(styles.gap).toBe('8px')
|
||||
expect(styles['padding-left']).toBe('16px')
|
||||
expect(styles['padding-top']).toBe('8px')
|
||||
expect(styles['font-size']).toBe('14px')
|
||||
expect(styles['font-weight']).toBe('500')
|
||||
expect(styles['border-radius']).toBe('6px')
|
||||
expect(styles.color).toBe('rgb(255, 255, 255)')
|
||||
})
|
||||
|
||||
test('resolves custom properties when assigned to real CSS properties', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -1,89 +1,23 @@
|
|||
import { describe, expect, it } from 'bun:test'
|
||||
|
||||
import { colorToCSS } from '@open-pencil/core/color'
|
||||
import {
|
||||
compileTailwindCSS,
|
||||
createHeadlessCSSRuntime,
|
||||
designDocumentToSceneGraph,
|
||||
sceneGraphToDesignDocument,
|
||||
serializeHTML,
|
||||
type DesignDocument
|
||||
serializeHTML
|
||||
} from '@open-pencil/dom-css'
|
||||
|
||||
const slate900 = colorToCSS({ r: 17 / 255, g: 24 / 255, b: 39 / 255, a: 1 })
|
||||
const slateShadow = colorToCSS({ r: 15 / 255, g: 23 / 255, b: 42 / 255, a: 0.12 })
|
||||
const slate200 = colorToCSS({ r: 229 / 255, g: 231 / 255, b: 235 / 255, a: 1 })
|
||||
|
||||
const cardHTML = `
|
||||
<article class="card">
|
||||
<h1 class="title">OpenPencil</h1>
|
||||
<p class="description">Design with code-shaped CSS.</p>
|
||||
</article>
|
||||
`
|
||||
|
||||
const cardCSS = `
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 24px;
|
||||
width: 320px;
|
||||
height: 180px;
|
||||
border: 1px solid ${slate200};
|
||||
border-radius: 16px;
|
||||
background: white;
|
||||
box-shadow: 0px 8px 24px 0px ${slateShadow};
|
||||
color: ${slate900};
|
||||
}
|
||||
.card .title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
}
|
||||
.description {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const cardDocument: DesignDocument = {
|
||||
type: 'document',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
attrs: { class: 'card' },
|
||||
computedStyle: {
|
||||
width: '320px',
|
||||
height: '160px',
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
gap: '12px',
|
||||
padding: '24px',
|
||||
'border-radius': '16px',
|
||||
'background-color': 'rgb(255, 255, 255)'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'h1',
|
||||
attrs: {},
|
||||
computedStyle: {
|
||||
color: 'rgb(17, 24, 39)',
|
||||
'font-size': '24px',
|
||||
'font-weight': '700',
|
||||
'line-height': '32px'
|
||||
},
|
||||
children: [{ type: 'text', text: 'OpenPencil' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
import {
|
||||
computedCardDocument,
|
||||
cssCardCSS,
|
||||
cssCardHTML,
|
||||
tailwindCardClasses
|
||||
} from '#tests/helpers/dom-css'
|
||||
|
||||
describe('@open-pencil/dom-css conversion', () => {
|
||||
it('projects a DesignDOM card into a scene graph', () => {
|
||||
const graph = designDocumentToSceneGraph(cardDocument)
|
||||
const graph = designDocumentToSceneGraph(computedCardDocument)
|
||||
const page = graph.getPages()[0]
|
||||
expect(page?.name).toBe('DesignDOM')
|
||||
|
||||
|
|
@ -106,7 +40,7 @@ describe('@open-pencil/dom-css conversion', () => {
|
|||
|
||||
it('projects a parsed and styled HTML/CSS card into a scene graph', async () => {
|
||||
const runtime = createHeadlessCSSRuntime()
|
||||
const document = await runtime.computeStyles(runtime.parseHTML(cardHTML), cardCSS)
|
||||
const document = await runtime.computeStyles(runtime.parseHTML(cssCardHTML), cssCardCSS)
|
||||
const graph = designDocumentToSceneGraph(document)
|
||||
const page = graph.getPages()[0]
|
||||
const card = page ? graph.getChildren(page.id)[0] : undefined
|
||||
|
|
@ -139,17 +73,7 @@ describe('@open-pencil/dom-css conversion', () => {
|
|||
|
||||
it('projects a Tailwind card through generated CSS into a scene graph', async () => {
|
||||
const runtime = createHeadlessCSSRuntime()
|
||||
const classes = [
|
||||
'flex',
|
||||
'flex-col',
|
||||
'gap-3',
|
||||
'w-80',
|
||||
'h-44',
|
||||
'p-6',
|
||||
'rounded-xl',
|
||||
'bg-white',
|
||||
'text-slate-900'
|
||||
]
|
||||
const classes = [...tailwindCardClasses]
|
||||
const document = await runtime.computeStyles(
|
||||
runtime.parseHTML(`<article class="${classes.join(' ')}"><h1>OpenPencil</h1></article>`),
|
||||
await compileTailwindCSS(classes)
|
||||
|
|
@ -170,7 +94,7 @@ describe('@open-pencil/dom-css conversion', () => {
|
|||
})
|
||||
|
||||
it('projects a scene graph back into DesignDOM', () => {
|
||||
const graph = designDocumentToSceneGraph(cardDocument)
|
||||
const graph = designDocumentToSceneGraph(computedCardDocument)
|
||||
const document = sceneGraphToDesignDocument(graph)
|
||||
const page = document.children[0]
|
||||
expect(page?.type).toBe('element')
|
||||
|
|
|
|||
|
|
@ -1,31 +1,12 @@
|
|||
import { describe, expect, it } from 'bun:test'
|
||||
|
||||
import { colorToCSS } from '@open-pencil/core/color'
|
||||
import {
|
||||
createCSSRuntime,
|
||||
createHeadlessCSSRuntime,
|
||||
serializeHTML,
|
||||
type DesignDocument
|
||||
} from '@open-pencil/dom-css'
|
||||
import { createCSSRuntime, createHeadlessCSSRuntime, serializeHTML } from '@open-pencil/dom-css'
|
||||
|
||||
const slate900 = colorToCSS({ r: 17 / 255, g: 24 / 255, b: 39 / 255, a: 1 })
|
||||
const slate700 = colorToCSS({ r: 31 / 255, g: 41 / 255, b: 55 / 255, a: 1 })
|
||||
|
||||
const documentFixture: DesignDocument = {
|
||||
type: 'document',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
attrs: { class: 'card', 'data-id': 'node-1' },
|
||||
children: [{ type: 'text', text: 'OpenPencil' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
import { DOM_CSS_COLORS, simpleCardDocument } from '#tests/helpers/dom-css'
|
||||
|
||||
describe('@open-pencil/dom-css', () => {
|
||||
it('serializes DesignDOM as HTML', () => {
|
||||
expect(serializeHTML(documentFixture)).toBe(
|
||||
expect(serializeHTML(simpleCardDocument)).toBe(
|
||||
'<div class="card" data-id="node-1">OpenPencil</div>'
|
||||
)
|
||||
})
|
||||
|
|
@ -34,7 +15,7 @@ describe('@open-pencil/dom-css', () => {
|
|||
const runtime = createCSSRuntime()
|
||||
|
||||
expect(runtime.kind).toBe('headless')
|
||||
expect(runtime.serializeHTML(documentFixture)).toBe(
|
||||
expect(runtime.serializeHTML(simpleCardDocument)).toBe(
|
||||
'<div class="card" data-id="node-1">OpenPencil</div>'
|
||||
)
|
||||
})
|
||||
|
|
@ -58,15 +39,15 @@ describe('@open-pencil/dom-css', () => {
|
|||
it('computes simple headless styles from CSSOM rules', async () => {
|
||||
const runtime = createHeadlessCSSRuntime()
|
||||
const document = await runtime.computeStyles(
|
||||
documentFixture,
|
||||
`.card { width: 320px; color: ${slate900}; } #missing { color: red; }`
|
||||
simpleCardDocument,
|
||||
`.card { width: 320px; color: ${DOM_CSS_COLORS.slate900}; } #missing { color: red; }`
|
||||
)
|
||||
const card = document.children[0]
|
||||
|
||||
expect(card?.type).toBe('element')
|
||||
if (card?.type !== 'element') return
|
||||
expect(card.computedStyle?.width).toBe('320px')
|
||||
expect(card.computedStyle?.color).toBe(slate900)
|
||||
expect(card.computedStyle?.color).toBe(DOM_CSS_COLORS.slate900)
|
||||
})
|
||||
|
||||
it('applies specificity, descendant selectors, child selectors, inheritance, and shorthands', async () => {
|
||||
|
|
@ -79,8 +60,8 @@ describe('@open-pencil/dom-css', () => {
|
|||
const document = await runtime.computeStyles(
|
||||
parsed,
|
||||
`
|
||||
article { color: ${slate900}; padding: 8px 16px; }
|
||||
.card { width: 300px; color: ${slate700}; }
|
||||
article { color: ${DOM_CSS_COLORS.slate900}; padding: 8px 16px; }
|
||||
.card { width: 300px; color: ${DOM_CSS_COLORS.slate700}; }
|
||||
article.card > header { gap: 12px; }
|
||||
.card .title { font-size: 24px; }
|
||||
#hero { width: 320px; background: white; }
|
||||
|
|
@ -91,7 +72,7 @@ describe('@open-pencil/dom-css', () => {
|
|||
expect(card?.type).toBe('element')
|
||||
if (card?.type !== 'element') return
|
||||
expect(card.computedStyle?.width).toBe('320px')
|
||||
expect(card.computedStyle?.color).toBe(slate700)
|
||||
expect(card.computedStyle?.color).toBe(DOM_CSS_COLORS.slate700)
|
||||
expect(card.computedStyle?.['padding-top']).toBe('8px')
|
||||
expect(card.computedStyle?.['padding-right']).toBe('16px')
|
||||
expect(card.computedStyle?.['background-color']).toBe('white')
|
||||
|
|
@ -100,12 +81,12 @@ describe('@open-pencil/dom-css', () => {
|
|||
expect(header?.type).toBe('element')
|
||||
if (header?.type !== 'element') return
|
||||
expect(header.computedStyle?.gap).toBe('12px')
|
||||
expect(header.computedStyle?.color).toBe(slate700)
|
||||
expect(header.computedStyle?.color).toBe(DOM_CSS_COLORS.slate700)
|
||||
|
||||
const title = header.children[0]
|
||||
expect(title?.type).toBe('element')
|
||||
if (title?.type !== 'element') return
|
||||
expect(title.computedStyle?.['font-size']).toBe('24px')
|
||||
expect(title.computedStyle?.color).toBe(slate700)
|
||||
expect(title.computedStyle?.color).toBe(DOM_CSS_COLORS.slate700)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { describe, expect, it } from 'bun:test'
|
|||
|
||||
import { compileTailwindCSS, createHeadlessCSSRuntime } from '@open-pencil/dom-css'
|
||||
|
||||
import { tailwindCardClasses } from '#tests/helpers/dom-css'
|
||||
|
||||
describe('@open-pencil/dom-css Tailwind', () => {
|
||||
it('compiles utility candidates through Tailwind', async () => {
|
||||
const css = await compileTailwindCSS('flex w-80 p-6 rounded-xl bg-white')
|
||||
const css = await compileTailwindCSS(tailwindCardClasses)
|
||||
|
||||
expect(css).toContain('.flex')
|
||||
expect(css).toContain('.w-80')
|
||||
|
|
@ -14,9 +16,9 @@ describe('@open-pencil/dom-css Tailwind', () => {
|
|||
|
||||
it('feeds Tailwind CSS variables through headless style computation', async () => {
|
||||
const runtime = createHeadlessCSSRuntime()
|
||||
const css = await compileTailwindCSS(['w-80', 'p-6'])
|
||||
const css = await compileTailwindCSS(tailwindCardClasses)
|
||||
const document = await runtime.computeStyles(
|
||||
runtime.parseHTML('<section class="w-80 p-6"></section>'),
|
||||
runtime.parseHTML(`<section class="${tailwindCardClasses.join(' ')}"></section>`),
|
||||
css
|
||||
)
|
||||
const element = document.children[0]
|
||||
|
|
|
|||
114
tests/helpers/dom-css.ts
Normal file
114
tests/helpers/dom-css.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { colorToCSS } from '@open-pencil/core/color'
|
||||
import type { DesignDocument } from '@open-pencil/dom-css'
|
||||
|
||||
export const DOM_CSS_COLORS = {
|
||||
slate900: colorToCSS({ r: 17 / 255, g: 24 / 255, b: 39 / 255, a: 1 }),
|
||||
slate700: colorToCSS({ r: 31 / 255, g: 41 / 255, b: 55 / 255, a: 1 }),
|
||||
slate200: colorToCSS({ r: 229 / 255, g: 231 / 255, b: 235 / 255, a: 1 }),
|
||||
slateShadow: colorToCSS({ r: 15 / 255, g: 23 / 255, b: 42 / 255, a: 0.12 })
|
||||
} as const
|
||||
|
||||
export const simpleCardDocument: DesignDocument = {
|
||||
type: 'document',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
attrs: { class: 'card', 'data-id': 'node-1' },
|
||||
children: [{ type: 'text', text: 'OpenPencil' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const cssCardHTML = `
|
||||
<article class="card">
|
||||
<h1 class="title">OpenPencil</h1>
|
||||
<p class="description">Design with code-shaped CSS.</p>
|
||||
</article>
|
||||
`
|
||||
|
||||
export const cssCardCSS = `
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 24px;
|
||||
width: 320px;
|
||||
height: 180px;
|
||||
border: 1px solid ${DOM_CSS_COLORS.slate200};
|
||||
border-radius: 16px;
|
||||
background: white;
|
||||
box-shadow: 0px 8px 24px 0px ${DOM_CSS_COLORS.slateShadow};
|
||||
color: ${DOM_CSS_COLORS.slate900};
|
||||
}
|
||||
.card .title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
}
|
||||
.description {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
export const computedCardDocument: DesignDocument = {
|
||||
type: 'document',
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
attrs: { class: 'card' },
|
||||
computedStyle: {
|
||||
width: '320px',
|
||||
height: '160px',
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
gap: '12px',
|
||||
padding: '24px',
|
||||
'border-radius': '16px',
|
||||
'background-color': 'rgb(255, 255, 255)'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'h1',
|
||||
attrs: {},
|
||||
computedStyle: {
|
||||
color: 'rgb(17, 24, 39)',
|
||||
'font-size': '24px',
|
||||
'font-weight': '700',
|
||||
'line-height': '32px'
|
||||
},
|
||||
children: [{ type: 'text', text: 'OpenPencil' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const tailwindCardClasses = [
|
||||
'flex',
|
||||
'flex-col',
|
||||
'gap-3',
|
||||
'w-80',
|
||||
'h-44',
|
||||
'p-6',
|
||||
'rounded-xl',
|
||||
'bg-white',
|
||||
'text-slate-900'
|
||||
] as const
|
||||
|
||||
export const tailwindButtonClasses = [
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'gap-2',
|
||||
'rounded-md',
|
||||
'bg-slate-900',
|
||||
'px-4',
|
||||
'py-2',
|
||||
'text-sm',
|
||||
'font-medium',
|
||||
'text-white'
|
||||
] as const
|
||||
Loading…
Reference in a new issue