import { CodeTransformer } from '../../src/transformer.js';
import { StyleConverter } from '../../src/converter.js';
import { ASTParser } from '../../src/parser.js';
describe('Optional Chaining in Theme References', () => {
let converter: StyleConverter;
beforeEach(() => {
converter = new StyleConverter();
});
test('should provide helpful error for theme.custom?.main', () => {
const sourceCode = `
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
button: {
color: theme.custom?.main,
minWidth: 65,
}
}));
export const TestComponent: React.FC = () => {
const classes = useStyles();
return ;
};
`;
const parser = new ASTParser(sourceCode);
const extractions = parser.extractMakeStylesCalls();
// This should throw an error during style conversion
expect(() => {
extractions.forEach(extraction => {
extraction.styles.forEach(style => {
converter.convertStyles(style.properties);
});
});
}).toThrow(/Unknown theme property: theme\.custom\.main\?/);
});
test('should handle theme.palette?.primary?.main with normal behavior', () => {
const sourceCode = `
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
text: {
color: theme.palette?.primary?.main,
}
}));
`;
const parser = new ASTParser(sourceCode);
const extractions = parser.extractMakeStylesCalls();
// Palette properties should work normally (not throw errors)
expect(() => {
extractions.forEach(extraction => {
extraction.styles.forEach(style => {
converter.convertStyles(style.properties);
});
});
}).not.toThrow();
});
test('should work normally with non-theme references', () => {
const sourceCode = `
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
button: {
minWidth: 65,
cursor: 'pointer',
}
}));
export const TestComponent: React.FC = () => {
const classes = useStyles();
return ;
};
`;
const parser = new ASTParser(sourceCode);
const extractions = parser.extractMakeStylesCalls();
const transformer = new CodeTransformer(sourceCode);
// Convert styles to get conversions
const conversions = new Map();
extractions.forEach(extraction => {
extraction.styles.forEach(style => {
const conversion = converter.convertStyles(style.properties);
conversions.set(`${extraction.hookName}.${style.name}`, conversion);
});
});
const result = transformer.transform(extractions, conversions);
expect(result.migratedCode).toContain('min-w-[65px]');
expect(result.migratedCode).toContain('cursor-pointer');
});
test('should handle custom theme mapping to resolve theme references', () => {
const customMapping = {
customThemeMapping: {
'theme.custom.main': 'text-blue-600'
}
};
const converterWithMapping = new StyleConverter(customMapping);
const sourceCode = `
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
button: {
color: theme.custom.main, // Should work with mapping
minWidth: 65,
}
}));
export const TestComponent: React.FC = () => {
const classes = useStyles();
return ;
};
`;
const parser = new ASTParser(sourceCode);
const extractions = parser.extractMakeStylesCalls();
const transformer = new CodeTransformer(sourceCode);
// Convert styles with custom mapping
const conversions = new Map();
extractions.forEach(extraction => {
extraction.styles.forEach(style => {
const conversion = converterWithMapping.convertStyles(style.properties);
conversions.set(`${extraction.hookName}.${style.name}`, conversion);
});
});
const result = transformer.transform(extractions, conversions);
expect(result.migratedCode).toContain('text-blue-600');
expect(result.migratedCode).toContain('min-w-[65px]');
});
});