Files
gui/pkgs/core/core-test/shorthandVariables.native.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

315 lines
9.0 KiB
TypeScript

import type { GetStyleResult } from '@hanzogui/web'
import { View, createGui, getSplitStyles } from '@hanzogui/core'
import { beforeAll, describe, expect, test } from 'vitest'
import config from '../config-default'
beforeAll(() => {
createGui(config.getDefaultGuiConfig('native'))
})
describe('shorthand variables - native', () => {
// on native, boxShadow/backgroundImage are parsed to RN object format
// filter stays as string (no RN object equivalent)
test('boxShadow with $variable resolves token to object format', () => {
const { style } = getSplitStylesFor({
boxShadow: '0 0 10px $white',
})
expect(style?.boxShadow).toEqual([
{ offsetX: 0, offsetY: 0, blurRadius: 10, color: '#fff' },
])
})
test('boxShadow with multiple tokens resolves all to objects', () => {
const { style } = getSplitStylesFor({
boxShadow: '0 0 10px $white, 0 0 20px $black',
})
expect(style?.boxShadow).toEqual([
{ offsetX: 0, offsetY: 0, blurRadius: 10, color: '#fff' },
{ offsetX: 0, offsetY: 0, blurRadius: 20, color: '#000' },
])
})
test('boxShadow without variables passed through as object', () => {
const { style } = getSplitStylesFor({
boxShadow: '0 0 10px red',
})
expect(style?.boxShadow).toEqual([
{ offsetX: 0, offsetY: 0, blurRadius: 10, color: 'red' },
])
})
test('boxShadow with unresolvable $variable keeps token string in object', () => {
const { style } = getSplitStylesFor({
boxShadow: '0 0 10px $nonexistent',
})
expect(style?.boxShadow).toEqual([
{ offsetX: 0, offsetY: 0, blurRadius: 10, color: '$nonexistent' },
])
})
test('filter with $variable resolves space token', () => {
const { style } = getSplitStylesFor({
filter: 'blur($2)',
})
// $2 in space = 7 (size 28 * 0.333 rounded)
expect(style?.filter).toBe('blur(7)')
})
test('filter without variables passed through unchanged', () => {
const { style } = getSplitStylesFor({
filter: 'brightness(1.2)',
})
expect(style?.filter).toBe('brightness(1.2)')
})
// backgroundImage - RN 0.76+ uses experimental_backgroundImage
// on native, parsed to object array format
test('backgroundImage with $variable resolves tokens to object format', () => {
const { style } = getSplitStylesFor({
backgroundImage: 'linear-gradient(to bottom, $white, $black)',
})
expect((style as any)?.experimental_backgroundImage).toEqual([
{
type: 'linear-gradient',
direction: 'to bottom',
colorStops: [{ color: '#fff' }, { color: '#000' }],
},
])
})
test('backgroundImage with angle and multiple color stops', () => {
const { style } = getSplitStylesFor({
backgroundImage: 'linear-gradient(45deg, $black 0%, $white 50%, $black 100%)',
})
expect((style as any)?.experimental_backgroundImage).toEqual([
{
type: 'linear-gradient',
direction: '45deg',
colorStops: [
{ color: '#000', positions: ['0%'] },
{ color: '#fff', positions: ['50%'] },
{ color: '#000', positions: ['100%'] },
],
},
])
})
test('backgroundImage without variables passed through as object', () => {
const { style } = getSplitStylesFor({
backgroundImage: 'linear-gradient(to bottom, red, blue)',
})
expect((style as any)?.experimental_backgroundImage).toEqual([
{
type: 'linear-gradient',
direction: 'to bottom',
colorStops: [{ color: 'red' }, { color: 'blue' }],
},
])
})
test('backgroundImage with unresolvable $variable keeps token in object', () => {
const { style } = getSplitStylesFor({
backgroundImage: 'linear-gradient($nonexistent, $white)',
})
// $nonexistent is not a valid direction, so parsed as color stop
expect((style as any)?.experimental_backgroundImage).toEqual([
{
type: 'linear-gradient',
colorStops: [{ color: '$nonexistent' }, { color: '#fff' }],
},
])
})
test('backgroundImage with $token/NN opacity modifier resolves to concrete rgba', () => {
const { style } = getSplitStylesFor({
backgroundImage: 'linear-gradient(180deg, $white/50, $white/0)',
})
expect((style as any)?.experimental_backgroundImage).toEqual([
{
type: 'linear-gradient',
direction: '180deg',
colorStops: [
{ color: 'rgba(255,255,255,0.5)' },
{ color: 'rgba(255,255,255,0)' },
],
},
])
})
})
describe('border shorthand - native', () => {
// border shorthand expands to borderWidth/borderStyle/borderColor on native
test('border with width, style and color', () => {
const { style } = getSplitStylesFor({
border: '1px solid red',
})
// border expands to borderWidth/borderStyle/borderColor which then expand further
expect(style?.borderTopWidth).toBe(1)
expect(style?.borderRightWidth).toBe(1)
expect(style?.borderBottomWidth).toBe(1)
expect(style?.borderLeftWidth).toBe(1)
expect(style?.borderStyle).toBe('solid')
expect(style?.borderTopColor).toBe('red')
expect(style?.borderRightColor).toBe('red')
expect(style?.borderBottomColor).toBe('red')
expect(style?.borderLeftColor).toBe('red')
})
test('border with $variable color resolves token', () => {
const { style } = getSplitStylesFor({
border: '2px dashed $white',
})
expect(style?.borderTopWidth).toBe(2)
expect(style?.borderStyle).toBe('dashed')
expect(style?.borderTopColor).toBe('#fff')
})
test('border with just width and style', () => {
const { style } = getSplitStylesFor({
border: '1px solid',
})
expect(style?.borderTopWidth).toBe(1)
expect(style?.borderStyle).toBe('solid')
})
test('border "none" expands to borderWidth: 0', () => {
const { style } = getSplitStylesFor({
border: 'none',
})
expect(style?.borderTopWidth).toBe(0)
})
test('border with different order (color first)', () => {
const { style } = getSplitStylesFor({
border: 'blue 2px dotted',
})
expect(style?.borderTopWidth).toBe(2)
expect(style?.borderStyle).toBe('dotted')
expect(style?.borderTopColor).toBe('blue')
})
})
describe('outline shorthand - native', () => {
// outline shorthand expands to outlineWidth/outlineStyle/outlineColor on native
test('outline with width, style and color', () => {
const { style } = getSplitStylesFor({
outline: '2px solid red',
})
expect(style?.outlineWidth).toBe(2)
expect(style?.outlineStyle).toBe('solid')
expect(style?.outlineColor).toBe('red')
})
test('outline with $variable color resolves token', () => {
const { style } = getSplitStylesFor({
outline: '2px dashed $white',
})
expect(style?.outlineWidth).toBe(2)
expect(style?.outlineStyle).toBe('dashed')
expect(style?.outlineColor).toBe('#fff')
})
test('outline "none" sets outlineWidth to 0', () => {
const { style } = getSplitStylesFor({
outline: 'none',
})
expect(style?.outlineWidth).toBe(0)
expect(style?.outlineStyle).toBe('none')
})
test('outline with just width and style', () => {
const { style } = getSplitStylesFor({
outline: '1px solid',
})
expect(style?.outlineWidth).toBe(1)
expect(style?.outlineStyle).toBe('solid')
})
})
describe('border shorthand with media queries - native', () => {
test('border in $sm applies when media state sm is true', () => {
const { style } = getSplitStylesFor({ $sm: { border: '2px solid green' } }, View, {
mediaState: { sm: true },
})
expect(style?.borderTopWidth).toBe(2)
expect(style?.borderStyle).toBe('solid')
expect(style?.borderTopColor).toBe('green')
})
test('border in $sm does not apply when media state sm is false', () => {
const { style } = getSplitStylesFor({ $sm: { border: '2px solid green' } }, View, {
mediaState: { sm: false },
})
expect(style?.borderTopWidth).toBeUndefined()
expect(style?.borderStyle).toBeUndefined()
expect(style?.borderTopColor).toBeUndefined()
})
test('border in $sm with token resolves when media matches', () => {
const { style } = getSplitStylesFor({ $sm: { border: '1px dashed $white' } }, View, {
mediaState: { sm: true },
})
expect(style?.borderTopWidth).toBe(1)
expect(style?.borderStyle).toBe('dashed')
expect(style?.borderTopColor).toBe('#fff')
})
})
function getSplitStylesFor(
props: Record<string, unknown>,
Component: { staticConfig: Parameters<typeof getSplitStyles>[1] } = View,
options?: { mediaState?: Record<string, boolean> }
): GetStyleResult {
return getSplitStyles(
props,
Component.staticConfig,
{} as any,
'',
{
hover: false,
press: false,
pressIn: false,
focus: false,
unmounted: true,
disabled: false,
focusVisible: false,
},
{
isAnimated: false,
resolveValues: 'value',
mediaState: options?.mediaState,
},
undefined,
undefined,
undefined,
undefined
)!
}