Files
gui/pkgs/core/core-test/rnStyleAlignment.web.test.tsx
zeekayandHanzo Dev 69c6e860d7 transplant onto tamagui v2.6.0, brand as a re-runnable transform
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.

This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.

174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.

Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
  - useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
    returns undefined there, so every Tamagui Dialog kills a prerender — the
    exact crash hanzo.app spent a day on.
  - data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.

Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.

Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.

Co-Authored-By: Hanzo Dev <[email protected]>
2026-07-30 18:25:06 -07:00

253 lines
8.7 KiB
TypeScript

import { beforeAll, describe, expect, test } from 'vitest'
import config from '../config-default'
import { View, createGui, StyleObjectValue } from '../web/src'
import { simplifiedGetSplitStyles } from './utils'
beforeAll(() => {
createGui(config.getDefaultGuiConfig())
})
// Helper to get style value from either style (no plugin) or rulesToInsert (with plugin)
function getStyleValue(
styles: ReturnType<typeof simplifiedGetSplitStyles>,
prop: string
): string | undefined {
// Check style first (when not using hanzogui plugin/CSS extraction)
if (styles.style?.[prop] !== undefined) {
return styles.style[prop] as string
}
// Check rulesToInsert (when using hanzogui plugin with CSS extraction)
if (styles.rulesToInsert) {
const rule = Object.values(styles.rulesToInsert).find(
(r: any) => r[0] === prop
) as any
return rule?.[StyleObjectValue]
}
return undefined
}
describe('RN 0.76+ Style Alignment - Web', () => {
// boxShadow and filter are string-only
describe('boxShadow', () => {
test('boxShadow string with tokens resolves', () => {
const styles = simplifiedGetSplitStyles(View, {
boxShadow: '0 0 10px $white',
})
const value = getStyleValue(styles, 'boxShadow')
expect(value).toBeDefined()
expect(value).toContain('var(--')
})
test('boxShadow string passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
boxShadow: '5px 5px 10px red',
})
const value = getStyleValue(styles, 'boxShadow')
expect(value).toBe('5px 5px 10px red')
})
test('boxShadow multiple shadows', () => {
const styles = simplifiedGetSplitStyles(View, {
boxShadow: '0 0 10px red, 0 0 20px blue',
})
const value = getStyleValue(styles, 'boxShadow')
expect(value).toBe('0 0 10px red, 0 0 20px blue')
})
test('boxShadow inset syntax', () => {
const styles = simplifiedGetSplitStyles(View, {
boxShadow: 'inset 0 2px 4px black',
})
const value = getStyleValue(styles, 'boxShadow')
expect(value).toBe('inset 0 2px 4px black')
})
test('boxShadow with multiple tokens resolves all', () => {
const styles = simplifiedGetSplitStyles(View, {
boxShadow: '0 0 10px $white, 0 0 20px $black',
})
const value = getStyleValue(styles, 'boxShadow')
expect(value).toBeDefined()
expect(value).not.toContain('$white')
expect(value).not.toContain('$black')
})
})
describe('filter', () => {
test('filter string passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
filter: 'brightness(1.2)',
})
const value = getStyleValue(styles, 'filter')
expect(value).toBe('brightness(1.2)')
})
test('filter with embedded tokens resolves', () => {
const styles = simplifiedGetSplitStyles(View, {
filter: 'blur($2)',
})
const value = getStyleValue(styles, 'filter')
// Token should be resolved
expect(value).not.toContain('$2')
})
test('filter multiple functions', () => {
const styles = simplifiedGetSplitStyles(View, {
filter: 'blur(10px) brightness(1.2)',
})
const value = getStyleValue(styles, 'filter')
expect(value).toBe('blur(10px) brightness(1.2)')
})
test('filter drop-shadow', () => {
const styles = simplifiedGetSplitStyles(View, {
filter: 'drop-shadow(5px 5px 10px red)',
})
const value = getStyleValue(styles, 'filter')
expect(value).toBe('drop-shadow(5px 5px 10px red)')
})
})
describe('mixBlendMode', () => {
test('mixBlendMode passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
mixBlendMode: 'multiply',
})
const value = getStyleValue(styles, 'mixBlendMode')
expect(value).toBe('multiply')
})
})
describe('isolation', () => {
test('isolation passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
isolation: 'isolate',
})
const value = getStyleValue(styles, 'isolation')
expect(value).toBe('isolate')
})
})
describe('boxSizing', () => {
test('boxSizing passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
boxSizing: 'content-box',
})
const value = getStyleValue(styles, 'boxSizing')
expect(value).toBe('content-box')
})
})
describe('outline props', () => {
test('outlineColor with token resolves', () => {
const styles = simplifiedGetSplitStyles(View, {
outlineColor: '$white',
})
const value = getStyleValue(styles, 'outlineColor')
expect(value).toContain('var(--')
})
test('outlineWidth passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
outlineWidth: 2,
})
const value = getStyleValue(styles, 'outlineWidth')
expect(value).toBe('2px')
})
test('outlineStyle passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
outlineStyle: 'dashed',
})
const value = getStyleValue(styles, 'outlineStyle')
expect(value).toBe('dashed')
})
test('outlineOffset passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
outlineOffset: 4,
})
const value = getStyleValue(styles, 'outlineOffset')
expect(value).toBe('4px')
})
})
describe('display contents', () => {
test('display contents passes through', () => {
const styles = simplifiedGetSplitStyles(View, {
display: 'contents',
})
const value = getStyleValue(styles, 'display')
expect(value).toBe('contents')
})
})
// atomic CSS requires one class per longhand. If a shorthand like `inset`
// emits a single _inset-0px class, it silently controls 4 sub-properties
// and breaks dedup/override against top/right/bottom/left.
describe('logical property expansion (atomic CSS correctness)', () => {
test('inset expands to top/right/bottom/left', () => {
const styles = simplifiedGetSplitStyles(View, { inset: 0 })
expect(getStyleValue(styles, 'top')).toBe('0px')
expect(getStyleValue(styles, 'right')).toBe('0px')
expect(getStyleValue(styles, 'bottom')).toBe('0px')
expect(getStyleValue(styles, 'left')).toBe('0px')
expect(getStyleValue(styles, 'inset')).toBeUndefined()
})
test('insetBlock expands to top/bottom', () => {
const styles = simplifiedGetSplitStyles(View, { insetBlock: 4 })
expect(getStyleValue(styles, 'top')).toBe('4px')
expect(getStyleValue(styles, 'bottom')).toBe('4px')
expect(getStyleValue(styles, 'insetBlock')).toBeUndefined()
})
test('insetBlockStart expands to top', () => {
const styles = simplifiedGetSplitStyles(View, { insetBlockStart: 8 })
expect(getStyleValue(styles, 'top')).toBe('8px')
expect(getStyleValue(styles, 'insetBlockStart')).toBeUndefined()
})
test('later top overrides inset (atomic merge)', () => {
const styles = simplifiedGetSplitStyles(View, { inset: 0, top: 10 })
expect(getStyleValue(styles, 'top')).toBe('10px')
expect(getStyleValue(styles, 'right')).toBe('0px')
expect(getStyleValue(styles, 'bottom')).toBe('0px')
expect(getStyleValue(styles, 'left')).toBe('0px')
})
test('marginBlock expands to marginTop/marginBottom', () => {
const styles = simplifiedGetSplitStyles(View, { marginBlock: 10 })
expect(getStyleValue(styles, 'marginTop')).toBe('10px')
expect(getStyleValue(styles, 'marginBottom')).toBe('10px')
expect(getStyleValue(styles, 'marginBlock')).toBeUndefined()
})
test('paddingBlock expands to paddingTop/paddingBottom', () => {
const styles = simplifiedGetSplitStyles(View, { paddingBlock: 12 })
expect(getStyleValue(styles, 'paddingTop')).toBe('12px')
expect(getStyleValue(styles, 'paddingBottom')).toBe('12px')
expect(getStyleValue(styles, 'paddingBlock')).toBeUndefined()
})
test('blockSize expands to height', () => {
const styles = simplifiedGetSplitStyles(View, { blockSize: 100 })
expect(getStyleValue(styles, 'height')).toBe('100px')
expect(getStyleValue(styles, 'blockSize')).toBeUndefined()
})
test('inlineSize expands to width', () => {
const styles = simplifiedGetSplitStyles(View, { inlineSize: 100 })
expect(getStyleValue(styles, 'width')).toBe('100px')
expect(getStyleValue(styles, 'inlineSize')).toBeUndefined()
})
test('minBlockSize expands to minHeight', () => {
const styles = simplifiedGetSplitStyles(View, { minBlockSize: 50 })
expect(getStyleValue(styles, 'minHeight')).toBe('50px')
expect(getStyleValue(styles, 'minBlockSize')).toBeUndefined()
})
})
})