Skip to content

Commit 3d607ee

Browse files
committed
Add case
1 parent 258f9e8 commit 3d607ee

File tree

2 files changed

+296
-1
lines changed

2 files changed

+296
-1
lines changed

src/__tests__/code.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,97 @@ describe('registerCodegen with viewport variant', () => {
356356
expect(responsiveResult).toBeDefined()
357357
})
358358

359+
it('should generate responsive component with multiple variants (viewport + size)', async () => {
360+
let capturedHandler: CodegenHandler | null = null
361+
362+
const figmaMock = {
363+
editorType: 'dev',
364+
mode: 'codegen',
365+
command: 'noop',
366+
codegen: {
367+
on: (_event: string, handler: CodegenHandler) => {
368+
capturedHandler = handler
369+
},
370+
},
371+
closePlugin: mock(() => {}),
372+
} as unknown as typeof figma
373+
374+
codeModule.registerCodegen(figmaMock)
375+
376+
expect(capturedHandler).not.toBeNull()
377+
if (capturedHandler === null) throw new Error('Handler not captured')
378+
379+
// COMPONENT_SET with both viewport and size variants
380+
const componentSetNode = {
381+
type: 'COMPONENT_SET',
382+
name: 'ResponsiveButton',
383+
visible: true,
384+
componentPropertyDefinitions: {
385+
viewport: {
386+
type: 'VARIANT',
387+
defaultValue: 'desktop',
388+
variantOptions: ['mobile', 'desktop'],
389+
},
390+
size: {
391+
type: 'VARIANT',
392+
defaultValue: 'md',
393+
variantOptions: ['sm', 'md', 'lg'],
394+
},
395+
},
396+
children: [
397+
{
398+
type: 'COMPONENT',
399+
name: 'viewport=mobile, size=md',
400+
visible: true,
401+
variantProperties: { viewport: 'mobile', size: 'md' },
402+
children: [],
403+
layoutMode: 'VERTICAL',
404+
width: 320,
405+
height: 100,
406+
},
407+
{
408+
type: 'COMPONENT',
409+
name: 'viewport=desktop, size=md',
410+
visible: true,
411+
variantProperties: { viewport: 'desktop', size: 'md' },
412+
children: [],
413+
layoutMode: 'HORIZONTAL',
414+
width: 1200,
415+
height: 100,
416+
},
417+
],
418+
defaultVariant: {
419+
type: 'COMPONENT',
420+
name: 'viewport=desktop, size=md',
421+
visible: true,
422+
variantProperties: { viewport: 'desktop', size: 'md' },
423+
children: [],
424+
},
425+
} as unknown as SceneNode
426+
427+
const handler = capturedHandler as CodegenHandler
428+
const result = await handler({
429+
node: componentSetNode,
430+
language: 'devup-ui',
431+
})
432+
433+
// Should include responsive components result
434+
const responsiveResult = result.find(
435+
(r: unknown) =>
436+
typeof r === 'object' &&
437+
r !== null &&
438+
'title' in r &&
439+
(r as { title: string }).title.includes('Responsive'),
440+
)
441+
expect(responsiveResult).toBeDefined()
442+
443+
// The generated code should include the size variant in the interface
444+
const resultWithCode = responsiveResult as { code: string } | undefined
445+
if (resultWithCode?.code) {
446+
expect(resultWithCode.code).toContain('size')
447+
}
448+
})
449+
359450
it('should generate responsive code for node with parent SECTION', async () => {
360451
let capturedHandler: CodegenHandler | null = null
361452

src/codegen/responsive/__tests__/ResponsiveCodegen.test.ts

Lines changed: 205 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const renderNodeMock = mock(
1111
`render:${component}:depth=${depth}:${JSON.stringify(props)}|${children.join(';')}`,
1212
)
1313

14+
const renderComponentMock = mock(
15+
(component: string, code: string, variants: Record<string, string>) =>
16+
`component:${component}:${JSON.stringify(variants)}|${code}`,
17+
)
18+
1419
// Mock Codegen class
1520
const mockGetTree = mock(
1621
async (): Promise<NodeTree> => ({
@@ -35,7 +40,10 @@ describe('ResponsiveCodegen', () => {
3540
let ResponsiveCodegen: typeof import('../ResponsiveCodegen').ResponsiveCodegen
3641

3742
beforeEach(async () => {
38-
mock.module('../../render', () => ({ renderNode: renderNodeMock }))
43+
mock.module('../../render', () => ({
44+
renderNode: renderNodeMock,
45+
renderComponent: renderComponentMock,
46+
}))
3947
mock.module('../../Codegen', () => ({ Codegen: MockCodegen }))
4048

4149
;({ ResponsiveCodegen } = await import('../ResponsiveCodegen'))
@@ -160,7 +168,203 @@ describe('ResponsiveCodegen', () => {
160168
it('static helpers detect section and parent section', () => {
161169
const section = { type: 'SECTION' } as unknown as SectionNode
162170
const frame = { type: 'FRAME', parent: section } as unknown as SceneNode
171+
const nonSection = { type: 'FRAME' } as unknown as SceneNode
172+
const nodeWithoutSectionParent = {
173+
type: 'FRAME',
174+
parent: { type: 'FRAME' },
175+
} as unknown as SceneNode
176+
163177
expect(ResponsiveCodegen.canGenerateResponsive(section)).toBeTrue()
178+
expect(ResponsiveCodegen.canGenerateResponsive(nonSection)).toBeFalse()
164179
expect(ResponsiveCodegen.hasParentSection(frame)).toEqual(section)
180+
expect(
181+
ResponsiveCodegen.hasParentSection(nodeWithoutSectionParent),
182+
).toBeNull()
183+
})
184+
185+
it('generateViewportResponsiveComponents returns empty when no viewport variant', async () => {
186+
const componentSet = {
187+
type: 'COMPONENT_SET',
188+
name: 'NoViewport',
189+
componentPropertyDefinitions: {
190+
size: {
191+
type: 'VARIANT',
192+
variantOptions: ['sm', 'md', 'lg'],
193+
},
194+
},
195+
children: [],
196+
} as unknown as ComponentSetNode
197+
198+
const result = await ResponsiveCodegen.generateViewportResponsiveComponents(
199+
componentSet,
200+
'NoViewport',
201+
)
202+
expect(result).toEqual([])
203+
})
204+
205+
it('generateViewportResponsiveComponents processes non-viewport variants', async () => {
206+
const componentSet = {
207+
type: 'COMPONENT_SET',
208+
name: 'MultiVariant',
209+
componentPropertyDefinitions: {
210+
viewport: {
211+
type: 'VARIANT',
212+
variantOptions: ['mobile', 'desktop'],
213+
},
214+
size: {
215+
type: 'VARIANT',
216+
variantOptions: ['sm', 'md', 'lg'],
217+
},
218+
},
219+
children: [
220+
{
221+
type: 'COMPONENT',
222+
name: 'viewport=mobile, size=md',
223+
variantProperties: { viewport: 'mobile', size: 'md' },
224+
children: [],
225+
layoutMode: 'VERTICAL',
226+
width: 320,
227+
height: 100,
228+
},
229+
{
230+
type: 'COMPONENT',
231+
name: 'viewport=desktop, size=md',
232+
variantProperties: { viewport: 'desktop', size: 'md' },
233+
children: [],
234+
layoutMode: 'HORIZONTAL',
235+
width: 1200,
236+
height: 100,
237+
},
238+
],
239+
} as unknown as ComponentSetNode
240+
241+
const result = await ResponsiveCodegen.generateViewportResponsiveComponents(
242+
componentSet,
243+
'MultiVariant',
244+
)
245+
246+
expect(result.length).toBeGreaterThan(0)
247+
// Check that the result includes the component name
248+
expect(result[0][0]).toBe('MultiVariant')
249+
// Check that the generated code includes the size variant type
250+
expect(result[0][1]).toContain('size')
251+
})
252+
253+
it('handles component without viewport in variantProperties', async () => {
254+
const componentSet = {
255+
type: 'COMPONENT_SET',
256+
name: 'PartialViewport',
257+
componentPropertyDefinitions: {
258+
viewport: {
259+
type: 'VARIANT',
260+
variantOptions: ['mobile', 'desktop'],
261+
},
262+
},
263+
children: [
264+
{
265+
type: 'COMPONENT',
266+
name: 'viewport=mobile',
267+
variantProperties: { viewport: 'mobile' },
268+
children: [],
269+
layoutMode: 'VERTICAL',
270+
width: 320,
271+
height: 100,
272+
},
273+
{
274+
type: 'COMPONENT',
275+
name: 'no-viewport',
276+
variantProperties: {}, // No viewport property
277+
children: [],
278+
layoutMode: 'HORIZONTAL',
279+
width: 1200,
280+
height: 100,
281+
},
282+
{
283+
type: 'FRAME', // Not a COMPONENT type
284+
name: 'frame-child',
285+
children: [],
286+
},
287+
],
288+
} as unknown as ComponentSetNode
289+
290+
const result = await ResponsiveCodegen.generateViewportResponsiveComponents(
291+
componentSet,
292+
'PartialViewport',
293+
)
294+
295+
// Should still generate responsive code for the valid component
296+
expect(result.length).toBeGreaterThanOrEqual(0)
297+
})
298+
299+
it('handles null sectionNode in constructor', () => {
300+
const generator = new ResponsiveCodegen(null)
301+
expect(generator).toBeDefined()
302+
})
303+
304+
it('sorts multiple non-viewport variants alphabetically', async () => {
305+
// Multiple non-viewport variants to trigger the sort callback
306+
const componentSet = {
307+
type: 'COMPONENT_SET',
308+
name: 'MultiPropVariant',
309+
componentPropertyDefinitions: {
310+
viewport: {
311+
type: 'VARIANT',
312+
variantOptions: ['mobile', 'desktop'],
313+
},
314+
size: {
315+
type: 'VARIANT',
316+
variantOptions: ['sm', 'md', 'lg'],
317+
},
318+
color: {
319+
type: 'VARIANT',
320+
variantOptions: ['red', 'blue', 'green'],
321+
},
322+
state: {
323+
type: 'VARIANT',
324+
variantOptions: ['default', 'hover', 'active'],
325+
},
326+
},
327+
children: [
328+
{
329+
type: 'COMPONENT',
330+
name: 'viewport=mobile, size=md, color=red, state=default',
331+
variantProperties: {
332+
viewport: 'mobile',
333+
size: 'md',
334+
color: 'red',
335+
state: 'default',
336+
},
337+
children: [],
338+
layoutMode: 'VERTICAL',
339+
width: 320,
340+
height: 100,
341+
},
342+
{
343+
type: 'COMPONENT',
344+
name: 'viewport=desktop, size=md, color=red, state=default',
345+
variantProperties: {
346+
viewport: 'desktop',
347+
size: 'md',
348+
color: 'red',
349+
state: 'default',
350+
},
351+
children: [],
352+
layoutMode: 'HORIZONTAL',
353+
width: 1200,
354+
height: 100,
355+
},
356+
],
357+
} as unknown as ComponentSetNode
358+
359+
const result = await ResponsiveCodegen.generateViewportResponsiveComponents(
360+
componentSet,
361+
'MultiPropVariant',
362+
)
363+
364+
expect(result.length).toBeGreaterThan(0)
365+
// Check that all non-viewport variants are in the interface
366+
expect(result[0][1]).toContain('size')
367+
expect(result[0][1]).toContain('color')
368+
expect(result[0][1]).toContain('state')
165369
})
166370
})

0 commit comments

Comments
 (0)