import ts from 'typescript'
import { parseJsx } from '../__tests__/utils'
import { printAst } from '../utils'
import { transformActionsToActionsSlot } from './transformActionsToActionsSlot'
export const mockedTransformer =
(kaioComponentName: string) =>
(context: ts.TransformationContext) =>
(rootNode: ts.Node): ts.Node => {
const visit = (node: ts.Node): ts.Node => {
if (ts.isJsxSelfClosingElement(node)) {
const componentProps: ts.JsxAttributeLike[] = node.attributes.properties.reduce<
ts.JsxAttributeLike[]
>((guidanceBlockProps, prop) => {
if (ts.isJsxAttribute(prop)) {
const propName = prop.name.getText()
const propValue = prop.initializer as ts.JsxExpression
if (propName === 'actions') {
const transformedActions = transformActionsToActionsSlot(
propValue.getChildren()[1] as ts.ObjectLiteralExpression,
true,
)
return transformedActions?.actionsSlotAttr
? [...guidanceBlockProps, transformedActions.actionsSlotAttr]
: guidanceBlockProps
}
return [...guidanceBlockProps, prop]
}
return [...guidanceBlockProps, prop]
}, [])
return ts.factory.createJsxSelfClosingElement(
ts.factory.createIdentifier(kaioComponentName),
undefined,
ts.factory.createJsxAttributes(componentProps),
)
}
return ts.visitEachChild(node, visit, context)
}
return ts.visitNode(rootNode, visit)
}
const transformInput = (
sourceFile: ts.SourceFile,
kaioComponentName: string = 'GuidanceBlock',
): string => {
const result = ts.transform(sourceFile, [mockedTransformer(kaioComponentName)])
const transformedSource = result.transformed[0] as ts.SourceFile
return printAst(transformedSource)
}
describe('transformActionsToActionsSlot()', () => {
it('transforms button-like and link-like actions prop into a Button and LinkButton', () => {
const inputAst = parseJsx(`
}
content={
Test
}
actions={{
primary: {
label: 'Primary action',
onClick: () => alert('click 1'),
},
secondary: {
label: 'Secondary action',
href: "#secondary"
},
}}
/>`)
const outputAst = parseJsx(`
}
content={Test
}
actionsSlot={<> alert('click 1')} variant="secondary" size="large" icon={ } iconPosition="end">Primary action Secondary action >}
/>
`)
expect(transformInput(inputAst)).toBe(printAst(outputAst))
})
it('transforms a primary action with all v1 Button props to expected Button outputs', () => {
const inputAst = parseJsx(`
}
content={Test
}
actions={{
primary: {
label: 'Learn more',
onClick: () => alert('tada: 🎉'),
tooltip: {
text: 'Opens in a new tab',
mood: 'cautionary',
},
badge: {
text: 'New',
},
destructive: true,
disabled: hasCondition ? true : false,
reversed: true,
icon: ,
iconPosition: 'end',
size: 'small',
working: true,
workingLabel: 'Loading...',
workingLabelHidden: true,
disableTabFocusAndIUnderstandTheAccessibilityImplications: true,
'data-custom-attr': 'custom-attr',
},
}}
/>`)
const outputAst = parseJsx(`
}
content={Test
}
actionsSlot={<> alert('tada: 🎉')}
tooltip={{text: 'Opens in a new tab',mood: 'cautionary',}}
badge={{ text: 'New', }}
isDisabled={hasCondition ? true : false}
isReversed
icon={ }
iconPosition='end'
size="medium"
isPending pendingLabel='Loading...'
hasHiddenPendingLabel
data-custom-attr='custom-attr'
variant="secondary"
>Learn more >}/>
`)
expect(transformInput(inputAst).replace(/\s+/g, ' ')).toBe(
printAst(outputAst).replace(/\s+/g, ' '),
)
})
it('Passes custom data attributes and old props to be caught by type errors', () => {
const inputAst = parseJsx(`
}
content={Test
}
actions={{
primary: {
label: 'Learn more',
onClick: () => alert('tada: 🎉'),
tooltip: {
text: 'Opens in a new tab',
mood: 'cautionary',
},
badge: {
text: 'New',
},
destructive: true,
disabled: hasCondition ? true : false,
reversed: true,
icon: ,
iconPosition: 'end',
size: 'small',
working: true,
workingLabel: 'Loading...',
workingLabelHidden: true,
disableTabFocusAndIUnderstandTheAccessibilityImplications: true,
'data-custom-attr': 'custom-attr',
},
}}
/>`)
const outputAst = parseJsx(`
}
content={Test
}
actionsSlot={<> alert('tada: 🎉')}
tooltip={{text: 'Opens in a new tab',mood: 'cautionary',}}
badge={{ text: 'New', }}
isDisabled={hasCondition ? true : false}
isReversed
icon={ }
iconPosition='end'
size="medium"
isPending pendingLabel='Loading...'
hasHiddenPendingLabel
data-custom-attr='custom-attr'
variant="secondary"
>Learn more >}/>
`)
expect(transformInput(inputAst).replace(/\s+/g, ' ')).toBe(
printAst(outputAst).replace(/\s+/g, ' '),
)
})
it('removes primary: true and applies variantOverride as secondary for primary actions', () => {
const inputAst = parseJsx(`
{},
primary: true,
},
}}
/>`)
const outputAst = parseJsx(`
{}} variant="secondary" size="large" icon={ } iconPosition="end">Action >}
/>
`)
expect(transformInput(inputAst)).toBe(printAst(outputAst))
})
it('removes secondary: true and applies variantOverride as secondary for primary actions', () => {
const inputAst = parseJsx(`
{},
secondary: true,
},
}}
/>`)
const outputAst = parseJsx(`
{}} variant="secondary" size="large" icon={ } iconPosition="end">Action >}
/>
`)
expect(transformInput(inputAst)).toBe(printAst(outputAst))
})
it('removes primary: true and applies variantOverride as tertiary for secondary actions', () => {
const inputAst = parseJsx(`
{},
primary: true,
},
}}
/>`)
const outputAst = parseJsx(`
{}} variant="tertiary" size="large">Secondary Action >}
/>
`)
expect(transformInput(inputAst)).toBe(printAst(outputAst))
})
it('removes secondary: true and applies variantOverride as tertiary for secondary actions', () => {
const inputAst = parseJsx(`
{},
secondary: true,
},
}}
/>`)
const outputAst = parseJsx(`
{}} variant="tertiary" size="large">Secondary Action >}
/>
`)
expect(transformInput(inputAst)).toBe(printAst(outputAst))
})
it('applies correct variants for both primary and secondary actions with primary: true', () => {
const inputAst = parseJsx(`
{},
primary: true,
},
secondary: {
label: 'Secondary',
onClick: () => {},
primary: true,
},
}}
/>`)
const outputAst = parseJsx(`
{}} variant="secondary" size="large" icon={ } iconPosition="end">Primary {}} variant="tertiary" size="large">Secondary >}
/>
`)
expect(transformInput(inputAst)).toBe(printAst(outputAst))
})
})