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
10 changes: 5 additions & 5 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[test]
coverage = true
coverageSkipTestFiles = true
coveragePathIgnorePatterns = ["dist/**"]
coverageThreshold = 0.9999
[test]
coverage = true
coverageSkipTestFiles = true
coveragePathIgnorePatterns = ["dist/**", "src/commands/exportPagesAndComponents.ts"]
coverageThreshold = 0.9999
4 changes: 4 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
{
"name": "Export Components",
"command": "export-components"
},
{
"name": "Export Pages and Components",
"command": "export-pages-and-components"
}
]
}
1 change: 1 addition & 0 deletions rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
entry: {
code: './src/code.ts',
},
performance: false,
resolve: {
extensions: ['.ts', '.js'],
},
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import * as devupModule from '../commands/devup'
import * as exportAssetsModule from '../commands/exportAssets'
import * as exportComponentsModule from '../commands/exportComponents'
import * as exportPagesAndComponentsModule from '../commands/exportPagesAndComponents'

let codeModule: typeof import('../code-impl')

Expand Down Expand Up @@ -38,6 +39,10 @@ beforeEach(() => {
spyOn(exportComponentsModule, 'exportComponents').mockImplementation(
mock(() => Promise.resolve()),
)
spyOn(
exportPagesAndComponentsModule,
'exportPagesAndComponents',
).mockImplementation(mock(() => Promise.resolve()))
})

afterEach(() => {
Expand All @@ -55,6 +60,7 @@ describe('runCommand', () => {
['import-devup-excel', ['excel'], 'importDevup'],
['export-assets', [], 'exportAssets'],
['export-components', [], 'exportComponents'],
['export-pages-and-components', [], 'exportPagesAndComponents'],
] as const)('dispatches %s', async (command, args, fn) => {
const closePlugin = mock(() => {})
const figmaMock = {
Expand All @@ -78,6 +84,11 @@ describe('runCommand', () => {
case 'exportComponents':
expect(exportComponentsModule.exportComponents).toHaveBeenCalled()
break
case 'exportPagesAndComponents':
expect(
exportPagesAndComponentsModule.exportPagesAndComponents,
).toHaveBeenCalled()
break
}
expect(closePlugin).toHaveBeenCalled()
})
Expand Down
4 changes: 4 additions & 0 deletions src/code-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { wrapComponent } from './codegen/utils/wrap-component'
import { exportDevup, importDevup } from './commands/devup'
import { exportAssets } from './commands/exportAssets'
import { exportComponents } from './commands/exportComponents'
import { exportPagesAndComponents } from './commands/exportPagesAndComponents'
import { getComponentName } from './utils'
import { toPascal } from './utils/to-pascal'

Expand Down Expand Up @@ -344,6 +345,9 @@ export function runCommand(ctx: typeof figma = figma) {
case 'export-components':
exportComponents().finally(() => ctx.closePlugin())
break
case 'export-pages-and-components':
exportPagesAndComponents().finally(() => ctx.closePlugin())
break
}
}

Expand Down
172 changes: 172 additions & 0 deletions src/commands/__tests__/exportPagesAndComponents.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { describe, expect, test } from 'bun:test'
import {
DEVUP_COMPONENTS,
extractCustomComponentImports,
extractImports,
generateImportStatements,
} from '../exportPagesAndComponents'

describe('DEVUP_COMPONENTS', () => {
test('should contain expected devup-ui components', () => {
expect(DEVUP_COMPONENTS).toContain('Box')
expect(DEVUP_COMPONENTS).toContain('Flex')
expect(DEVUP_COMPONENTS).toContain('Text')
expect(DEVUP_COMPONENTS).toContain('Image')
expect(DEVUP_COMPONENTS).toContain('Grid')
expect(DEVUP_COMPONENTS).toContain('VStack')
expect(DEVUP_COMPONENTS).toContain('Center')
})
})

describe('extractImports', () => {
test('should extract Box import', () => {
const result = extractImports([['Test', '<Box>Hello</Box>']])
expect(result).toContain('Box')
})

test('should extract multiple devup-ui components', () => {
const result = extractImports([
['Test', '<Box><Flex><Text>Hello</Text></Flex></Box>'],
])
expect(result).toContain('Box')
expect(result).toContain('Flex')
expect(result).toContain('Text')
})

test('should extract keyframes with parenthesis', () => {
const result = extractImports([
['Test', '<Box animationName={keyframes({ "0%": { opacity: 0 } })} />'],
])
expect(result).toContain('keyframes')
expect(result).toContain('Box')
})

test('should extract keyframes with template literal', () => {
const result = extractImports([
['Test', '<Box animationName={keyframes`from { opacity: 0 }`} />'],
])
expect(result).toContain('keyframes')
})

test('should not extract keyframes when not present', () => {
const result = extractImports([['Test', '<Box w="100px" />']])
expect(result).not.toContain('keyframes')
})

test('should return sorted imports', () => {
const result = extractImports([
['Test', '<VStack><Box><Center /></Box></VStack>'],
])
expect(result).toEqual(['Box', 'Center', 'VStack'])
})

test('should not include duplicates', () => {
const result = extractImports([
['Test1', '<Box>A</Box>'],
['Test2', '<Box>B</Box>'],
])
expect(result.filter((x) => x === 'Box').length).toBe(1)
})

test('should handle self-closing tags', () => {
const result = extractImports([['Test', '<Image />']])
expect(result).toContain('Image')
})

test('should handle tags with spaces', () => {
const result = extractImports([['Test', '<Grid rows={2}>']])
expect(result).toContain('Grid')
})
})

describe('extractCustomComponentImports', () => {
test('should extract custom component', () => {
const result = extractCustomComponentImports([
['Test', '<Box><CustomButton /></Box>'],
])
expect(result).toContain('CustomButton')
})

test('should extract multiple custom components', () => {
const result = extractCustomComponentImports([
['Test', '<CustomA><CustomB /><CustomC /></CustomA>'],
])
expect(result).toContain('CustomA')
expect(result).toContain('CustomB')
expect(result).toContain('CustomC')
})

test('should not include devup-ui components', () => {
const result = extractCustomComponentImports([
['Test', '<Box><Flex><CustomCard /></Flex></Box>'],
])
expect(result).toContain('CustomCard')
expect(result).not.toContain('Box')
expect(result).not.toContain('Flex')
})

test('should return sorted imports', () => {
const result = extractCustomComponentImports([
['Test', '<Zebra /><Apple /><Mango />'],
])
expect(result).toEqual(['Apple', 'Mango', 'Zebra'])
})

test('should not include duplicates', () => {
const result = extractCustomComponentImports([
['Test1', '<SharedButton />'],
['Test2', '<SharedButton />'],
])
expect(result.filter((x) => x === 'SharedButton').length).toBe(1)
})

test('should return empty array when no custom components', () => {
const result = extractCustomComponentImports([
['Test', '<Box><Flex>Hello</Flex></Box>'],
])
expect(result).toEqual([])
})
})

describe('generateImportStatements', () => {
test('should generate devup-ui import statement', () => {
const result = generateImportStatements([['Test', '<Box><Flex /></Box>']])
expect(result).toContain("import { Box, Flex } from '@devup-ui/react'")
})

test('should generate custom component import statements', () => {
const result = generateImportStatements([
['Test', '<Box><CustomButton /></Box>'],
])
expect(result).toContain("import { Box } from '@devup-ui/react'")
expect(result).toContain(
"import { CustomButton } from '@/components/CustomButton'",
)
})

test('should generate multiple custom component imports on separate lines', () => {
const result = generateImportStatements([
['Test', '<Box><ButtonA /><ButtonB /></Box>'],
])
expect(result).toContain("import { ButtonA } from '@/components/ButtonA'")
expect(result).toContain("import { ButtonB } from '@/components/ButtonB'")
})

test('should return empty string when no imports', () => {
const result = generateImportStatements([['Test', 'just text']])
expect(result).toBe('')
})

test('should include keyframes in devup-ui import', () => {
const result = generateImportStatements([
['Test', '<Box animation={keyframes({})} />'],
])
expect(result).toContain('keyframes')
expect(result).toContain("from '@devup-ui/react'")
})

test('should end with double newline when has imports', () => {
const result = generateImportStatements([['Test', '<Box />']])
expect(result.endsWith('\n\n')).toBe(true)
})
})
Loading