Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 126 additions & 2 deletions src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { describe, expect, it, test } from 'bun:test'
import { getComponentName, space } from '../utils'
import { afterEach, describe, expect, it, test } from 'bun:test'
import {
getComponentName,
propsToPropsWithTypography,
resetTextStyleCache,
space,
} from '../utils'

// Minimal figma global for propsToPropsWithTypography tests
if (!(globalThis as { figma?: unknown }).figma) {
;(globalThis as { figma?: unknown }).figma = {
getLocalTextStylesAsync: () => Promise.resolve([]),
getStyleByIdAsync: () => Promise.resolve(null),
} as unknown as typeof figma
}

describe('space', () => {
it('should create space', () => {
Expand All @@ -9,6 +22,117 @@ describe('space', () => {
})
})

describe('propsToPropsWithTypography', () => {
afterEach(() => {
resetTextStyleCache()
})

it('should apply typography from resolved cache (sync fast path)', async () => {
const origGetLocal = figma.getLocalTextStylesAsync
const origGetStyle = figma.getStyleByIdAsync
figma.getLocalTextStylesAsync = () =>
Promise.resolve([{ id: 'ts-1' } as unknown as TextStyle]) as ReturnType<
typeof figma.getLocalTextStylesAsync
>
figma.getStyleByIdAsync = (id: string) =>
Promise.resolve(
id === 'ts-1'
? ({ id: 'ts-1', name: 'Typography/Body' } as unknown as BaseStyle)
: null,
) as ReturnType<typeof figma.getStyleByIdAsync>

// First call: populates async caches + resolved caches via .then()
const r1 = await propsToPropsWithTypography(
{ fontFamily: 'Arial', fontSize: 16, w: 100, h: 50 },
'ts-1',
)
expect(r1.typography).toBe('body')
expect(r1.fontFamily).toBeUndefined()
expect(r1.w).toBeUndefined()

// Second call: hits sync resolved-value cache (lines 71-72)
const r2 = await propsToPropsWithTypography(
{ fontFamily: 'Inter', fontSize: 14, w: 200, h: 60 },
'ts-1',
)
expect(r2.typography).toBe('body')
expect(r2.fontFamily).toBeUndefined()
expect(r2.w).toBeUndefined()

figma.getLocalTextStylesAsync = origGetLocal
figma.getStyleByIdAsync = origGetStyle
})

it('should return early from sync path when textStyleId not in resolved set', async () => {
const origGetLocal = figma.getLocalTextStylesAsync
const origGetStyle = figma.getStyleByIdAsync
figma.getLocalTextStylesAsync = () =>
Promise.resolve([{ id: 'ts-1' } as unknown as TextStyle]) as ReturnType<
typeof figma.getLocalTextStylesAsync
>
figma.getStyleByIdAsync = () =>
Promise.resolve(null) as ReturnType<typeof figma.getStyleByIdAsync>

// First call: populates resolved cache
await propsToPropsWithTypography(
{ fontFamily: 'Arial', w: 100, h: 50 },
'ts-1',
)

// Second call with unknown textStyleId — hits else branch (lines 75-76)
const r = await propsToPropsWithTypography(
{ fontFamily: 'Inter', w: 200, h: 60 },
'ts-unknown',
)
expect(r.fontFamily).toBe('Inter')
expect(r.typography).toBeUndefined()
expect(r.w).toBeUndefined()

// Third call with empty textStyleId — also hits else branch
const r2 = await propsToPropsWithTypography(
{ fontFamily: 'Mono', w: 300, h: 70 },
'',
)
expect(r2.fontFamily).toBe('Mono')
expect(r2.typography).toBeUndefined()

figma.getLocalTextStylesAsync = origGetLocal
figma.getStyleByIdAsync = origGetStyle
})

it('should return props without typography when style resolves to null', async () => {
const origGetLocal = figma.getLocalTextStylesAsync
const origGetStyle = figma.getStyleByIdAsync
figma.getLocalTextStylesAsync = () =>
Promise.resolve([
{ id: 'ts-null' } as unknown as TextStyle,
]) as ReturnType<typeof figma.getLocalTextStylesAsync>
figma.getStyleByIdAsync = () =>
Promise.resolve(null) as ReturnType<typeof figma.getStyleByIdAsync>

// First call: populates caches, style is null
const r1 = await propsToPropsWithTypography(
{ fontFamily: 'Arial', fontSize: 16, w: 100, h: 50 },
'ts-null',
)
expect(r1.typography).toBeUndefined()
expect(r1.fontFamily).toBe('Arial')
expect(r1.w).toBeUndefined()

// Second call: sync path, styleByIdResolved has null → style is falsy, skip applyTypography
const r2 = await propsToPropsWithTypography(
{ fontFamily: 'Inter', fontSize: 14, w: 200, h: 60 },
'ts-null',
)
expect(r2.typography).toBeUndefined()
expect(r2.fontFamily).toBe('Inter')
expect(r2.w).toBeUndefined()

figma.getLocalTextStylesAsync = origGetLocal
figma.getStyleByIdAsync = origGetStyle
})
})

describe('getComponentName', () => {
test.each([
{
Expand Down
2 changes: 2 additions & 0 deletions src/code-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
resetMainComponentCache,
} from './codegen/Codegen'
import { resetGetPropsCache } from './codegen/props'
import { resetChildAnimationCache } from './codegen/props/reaction'
import { resetSelectorPropsCache } from './codegen/props/selector'
import { ResponsiveCodegen } from './codegen/responsive/ResponsiveCodegen'
import { nodeProxyTracker } from './codegen/utils/node-proxy'
Expand Down Expand Up @@ -139,6 +140,7 @@ export function registerCodegen(ctx: typeof figma) {
perfReset()
resetGetPropsCache()
resetSelectorPropsCache()
resetChildAnimationCache()
resetVariableCache()
resetTextStyleCache()
resetMainComponentCache()
Expand Down
Loading