diff --git a/packages/autocomplete/src/create.ts b/packages/autocomplete/src/create.ts index 949fd1b276..bd90126ef6 100644 --- a/packages/autocomplete/src/create.ts +++ b/packages/autocomplete/src/create.ts @@ -62,7 +62,7 @@ export function createAutocomplete(uno: UnoGenerator, options: AutocompleteOptio } async function suggest(input: string, allowsEmptyInput = false) { - if (!allowsEmptyInput && input.length < 2) + if (!allowsEmptyInput && input.length < 1) return [] if (cache.has(input)) return cache.get(input)! diff --git a/packages/autocomplete/src/parse.ts b/packages/autocomplete/src/parse.ts index c3f906ebd9..24d630d7f8 100644 --- a/packages/autocomplete/src/parse.ts +++ b/packages/autocomplete/src/parse.ts @@ -1,6 +1,6 @@ import { uniq } from '@unocss/core' import { Fzf } from 'fzf' -import type { AutoCompleteMatchType, AutocompleteTemplatePart, ParsedAutocompleteTemplate } from './types' +import type { AutoCompleteMatchType, AutocompleteTemplateGroup, AutocompleteTemplatePart, AutocompleteTemplateStatic, ParsedAutocompleteTemplate } from './types' import { cartesian } from './utils' export const shorthands: Record = { @@ -72,10 +72,12 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth }) }, (str) => { - parts.push({ - type: 'static', - value: str, - }) + if (str !== '-') { + parts.push({ + type: 'static', + value: str, + }) + } }, ) } @@ -99,36 +101,35 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth function suggest(input: string, matchType: AutoCompleteMatchType = 'prefix') { if (input.length > 1 && matchType === 'fuzzy') return fzf.find(input).map(i => i.item) - let rest = input - let matched = '' + let rest = input.replace(/-/g, '') + let matched: string[] = [''] let combinations: string[] = [] const tempParts = [...parts] while (tempParts.length) { const part = tempParts.shift()! if (part.type === 'static') { - if (combinations.length) - combinations = combinations.map(i => i + part.value) - if (part.value.startsWith(rest) && part.value !== rest && !combinations.length) { - combinations = [part.value] - break - } - else if (!rest.startsWith(part.value)) { - break - } - matched += part.value - rest = rest.slice(part.value.length) + matched = matched.map(m => m += part.value) + rest = rest.slice(part.value.endsWith('-') ? part.value.length - 1 : part.value.length) } else if (part.type === 'group') { const fullMatched = part.values.find(i => i && rest.startsWith(i)) - if (fullMatched != null) { - matched += fullMatched + if (fullMatched != null && (tempParts[0] && tempParts[0].type !== 'static')) { + matched = matched.map(m => m += `${fullMatched}-`) rest = rest.slice(fullMatched.length) } else { - combinations = part.values.filter(i => i.startsWith(rest)) - if (tempParts[0]?.type !== 'static') + if (tempParts[0] && tempParts[0].type !== 'static') { + const values = (tempParts[0] as AutocompleteTemplateGroup).values + if (values) + matched = matched.map(m => values.map(n => m + n + tempParts[1] ? '-' : '')).flat() break + } + else if (tempParts[0]) { + return part.values.map(p => (tempParts[0] as AutocompleteTemplateStatic).value.slice(1).split('|').map(t => `${p}-${t}`)).flat() + } + combinations = part.values.filter(p => p.startsWith(rest)) + break } } else if (part.type === 'theme') { @@ -136,25 +137,31 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth .filter(i => i && !ignoredThemeKeys.includes(i) && i[0] !== '_') const fullMatched = keys.find(i => i && rest.startsWith(i)) if (fullMatched != null) { - matched += fullMatched rest = rest.slice(fullMatched.length) const subObjects = part.objects.map(i => i[fullMatched]) .filter((i): i is Record => !!i && typeof i === 'object') if (subObjects.length) { + matched = matched.map(m => m += `${fullMatched}-`) tempParts.unshift({ - type: 'static', - value: '-', - }, { type: 'theme', objects: subObjects, }) } + else { + combinations = keys.filter(i => i.startsWith(rest)) + } } else { - combinations = keys.filter(i => i.startsWith(rest)) - if (tempParts[0]?.type !== 'static') - break + if (tempParts[0] && tempParts[0].type !== 'static') { + const values = (tempParts[0] as AutocompleteTemplateGroup).values + if (values) + matched = matched.map(m => values.map(n => m + n + tempParts[1] ? '-' : '')).flat() + } + else { + combinations = keys.filter(i => i.startsWith(rest)) + } + break } } } @@ -188,7 +195,7 @@ export function parseAutocomplete(template: string, theme: any = {}, extraShorth // } // } - return combinations.map(i => matched + i) + return combinations.map(i => matched.map(m => m + i)).flat() .filter(i => i.length >= input.length) } } diff --git a/packages/core/src/generator/index.ts b/packages/core/src/generator/index.ts index 81dc9c636e..d3202170d8 100644 --- a/packages/core/src/generator/index.ts +++ b/packages/core/src/generator/index.ts @@ -626,7 +626,7 @@ export class UnoGenerator { // expand nested shortcuts if (isString(result)) - result = expandVariantGroup(result.trim()).split(/\s+/g) + result = expandVariantGroup(result.trim(), undefined, undefined, true).split(/\s+/g) // expand nested shortcuts with variants if (!result) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5a260e4abe..ec231a99ad 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -654,7 +654,8 @@ export interface SourceCodeTransformer { transform: ( code: MagicString, id: string, - ctx: UnocssPluginContext + ctx: UnocssPluginContext, + onlyAttribute?: boolean ) => Awaitable<{ highlightAnnotations?: HighlightAnnotation[] } | void> } diff --git a/packages/core/src/utils/variant-group.ts b/packages/core/src/utils/variant-group.ts index 8575355e08..5a83f8d6e4 100644 --- a/packages/core/src/utils/variant-group.ts +++ b/packages/core/src/utils/variant-group.ts @@ -17,18 +17,28 @@ interface VariantGroup { items: HighlightAnnotation[] } -export function parseVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) { +const regexAttribute = /(<[\w\-_]+)\s+(([^<>"']|"[^"]*"|'[^']*'|<)*)\/?>/gm +export function parseVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5, onlyAttribute?: boolean) { const regexClassGroup = makeRegexClassGroup(separators) let hasChanged let content = str.toString() const prefixes = new Set() const groupsByOffset = new Map() - + const isAttributor = (offset: number) => { + for (const match of content.matchAll(regexAttribute)) { + const index = match.index! + if (index + match[1].length < offset && (index + match[0].length > offset)) + return true + } + return false + } do { hasChanged = false content = content.replace( regexClassGroup, (from, pre: string, sep: string, body: string, groupOffset: number) => { + if (!onlyAttribute && !isAttributor(groupOffset)) + return from if (!separators.includes(sep)) return from @@ -135,10 +145,10 @@ export function collapseVariantGroup(str: string, prefixes: string[]): string { .join(' ') } -export function expandVariantGroup(str: string, separators?: string[], depth?: number): string -export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number): MagicString -export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5) { - const res = parseVariantGroup(str, separators, depth) +export function expandVariantGroup(str: string, separators?: string[], depth?: number, onlyAttribute?: boolean): string +export function expandVariantGroup(str: MagicString, separators?: string[], depth?: number, onlyAttribute?: boolean): MagicString +export function expandVariantGroup(str: string | MagicString, separators = ['-', ':'], depth = 5, onlyAttribute?: boolean) { + const res = parseVariantGroup(str, separators, depth, onlyAttribute) return typeof str === 'string' ? res.expanded : str diff --git a/packages/shared-common/src/index.ts b/packages/shared-common/src/index.ts index f59d4ef60b..2cd0b8efbe 100644 --- a/packages/shared-common/src/index.ts +++ b/packages/shared-common/src/index.ts @@ -238,9 +238,10 @@ export async function getMatchedPositionsFromCode( const transformers = uno.config.transformers?.filter(i => !ignoreTransformers.includes(i.name)) const annotations = [] + for (const enforce of ['pre', 'default', 'post']) { for (const i of transformers?.filter(i => (i.enforce ?? 'default') === enforce) || []) { - const result = await i.transform(s, id, ctx) + const result = await i.transform(s, id, ctx, true) const _annotations = result?.highlightAnnotations if (_annotations) annotations.push(..._annotations) diff --git a/packages/shared-integration/src/sort-rules.ts b/packages/shared-integration/src/sort-rules.ts index cabc6af4e9..fb2392eed0 100644 --- a/packages/shared-integration/src/sort-rules.ts +++ b/packages/shared-integration/src/sort-rules.ts @@ -11,7 +11,7 @@ export async function sortRules(rules: string, uno: UnoGenerator) { // const hasAttributify = !!uno.config.presets.find(i => i.name === '@unocss/preset-attributify') // const hasVariantGroup = !!uno.config.transformers?.find(i => i.name === '@unocss/transformer-variant-group') - const expandedResult = parseVariantGroup(rules) // todo read seperators from config + const expandedResult = parseVariantGroup(rules, undefined, undefined, true) // todo read seperators from config rules = expandedResult.expanded const result = await Promise.all(rules.split(/\s+/g) diff --git a/packages/svelte-scoped/src/_preprocess/transformApply/getUtils.ts b/packages/svelte-scoped/src/_preprocess/transformApply/getUtils.ts index d39a147c71..3eb7b1b772 100644 --- a/packages/svelte-scoped/src/_preprocess/transformApply/getUtils.ts +++ b/packages/svelte-scoped/src/_preprocess/transformApply/getUtils.ts @@ -4,7 +4,7 @@ import { expandVariantGroup, warnOnce } from '@unocss/core' type Writeable = { -readonly [P in keyof T]: T[P] } export async function getUtils(body: string, uno: UnoGenerator): Promise { - const classNames = expandVariantGroup(body) + const classNames = expandVariantGroup(body, undefined, undefined, true) .split(/\s+/g) .map(className => className.trim().replace(/\\/, '')) diff --git a/packages/svelte-scoped/src/_preprocess/transformClasses/processClassBody.ts b/packages/svelte-scoped/src/_preprocess/transformClasses/processClassBody.ts index 4839fdd2b2..6b84047c74 100644 --- a/packages/svelte-scoped/src/_preprocess/transformClasses/processClassBody.ts +++ b/packages/svelte-scoped/src/_preprocess/transformClasses/processClassBody.ts @@ -13,7 +13,7 @@ export async function processClassBody( uno: UnoGenerator, filename: string, ): Promise> { - const expandedBody = expandVariantGroup(body) + const expandedBody = expandVariantGroup(body, undefined, undefined, true) const { rulesToGenerate: rulesFromExpressions, restOfBody, updatedExpressions } = await processExpressions(expandedBody, options, uno, filename) const { rulesToGenerate: rulesFromRegularClasses, ignore } = await sortClassesIntoCategories(restOfBody, options, uno, filename) diff --git a/packages/transformer-directives/src/apply.ts b/packages/transformer-directives/src/apply.ts index 49492eab13..7a27afb754 100644 --- a/packages/transformer-directives/src/apply.ts +++ b/packages/transformer-directives/src/apply.ts @@ -37,7 +37,7 @@ export async function parseApply({ code, uno, offset, applyVariable }: Transform if (!body) return - const classNames = expandVariantGroup(body) + const classNames = expandVariantGroup(body, undefined, undefined, true) .split(/\s+/g) .map(className => className.trim().replace(/\\/, '')) @@ -53,7 +53,7 @@ export async function parseApply({ code, uno, offset, applyVariable }: Transform if (target) target[2] += item[2] else - // use spread operator to prevent reassign to uno internal cache + // use spread operator to prevent reassign to uno internal cache acc.push([...item] as Writeable) return acc }, [] as Writeable[]) @@ -88,7 +88,6 @@ export async function parseApply({ code, uno, offset, applyVariable }: Transform }) newSelector = generate(prelude) } - let css = `${newSelector}{${body}}` if (parent) css = `${parent}{${css}}` diff --git a/packages/transformer-variant-group/src/index.ts b/packages/transformer-variant-group/src/index.ts index e8cc7f9106..d23c9abf39 100644 --- a/packages/transformer-variant-group/src/index.ts +++ b/packages/transformer-variant-group/src/index.ts @@ -25,8 +25,8 @@ export default function transformerVariantGroup( return { name: '@unocss/transformer-variant-group', enforce: 'pre', - transform(s) { - const result = parseVariantGroup(s, options.separators) + transform(s, id, ctx, onlyAttribute?: boolean) { + const result = parseVariantGroup(s, options.separators, undefined, onlyAttribute) return { get highlightAnnotations() { return [...result.groupsByOffset.values()].flatMap(group => group.items) diff --git a/test/__snapshots__/transformer-variant-group.test.ts.snap b/test/__snapshots__/transformer-variant-group.test.ts.snap index cdaf0cf29a..0ac64b7300 100644 --- a/test/__snapshots__/transformer-variant-group.test.ts.snap +++ b/test/__snapshots__/transformer-variant-group.test.ts.snap @@ -6,15 +6,15 @@ exports[`transformer-variant-group > basic > "!hover:(m-2 p-2)" 1`] = ` { "className": "!hover:m-2", "length": 3, - "offset": 8, + "offset": 13, }, { "className": "!hover:p-2", "length": 3, - "offset": 12, + "offset": 17, }, ], - "transformed": "!hover:m-2 !hover:p-2", + "transformed": "
!hover:(m-2 p-2)
", } `; @@ -24,15 +24,15 @@ exports[`transformer-variant-group > basic > "", } `; @@ -42,15 +42,15 @@ exports[`transformer-variant-group > basic > "", } `; @@ -60,25 +60,25 @@ exports[`transformer-variant-group > basic > "[&]:(w-4 h-4) [&]:(w-4 h-4)" 1`] = { "className": "[&]:w-4", "length": 3, - "offset": 5, + "offset": 10, }, { "className": "[&]:h-4", "length": 3, - "offset": 9, + "offset": 14, }, { "className": "[&]:w-4", "length": 3, - "offset": 19, + "offset": 24, }, { "className": "[&]:h-4", "length": 3, - "offset": 23, + "offset": 28, }, ], - "transformed": "[&]:w-4 [&]:h-4 [&]:w-4 [&]:h-4", + "transformed": "
[&]:(w-4 h-4) [&]:(w-4 h-4)
", } `; @@ -88,35 +88,35 @@ exports[`transformer-variant-group > basic > "a1 a2:(b1 b2:(c1 c2-(d1 d2) c3) b3 { "className": "a2:b1", "length": 2, - "offset": 7, + "offset": 12, }, { "className": "a2:b2:c1", "length": 2, - "offset": 14, + "offset": 19, }, { "className": "a2:b2:c2-d1", "length": 2, - "offset": 21, + "offset": 26, }, { "className": "a2:b2:c2-d2", "length": 2, - "offset": 24, + "offset": 29, }, { "className": "a2:b2:c3", "length": 2, - "offset": 28, + "offset": 33, }, { "className": "a2:b3", "length": 2, - "offset": 32, + "offset": 37, }, ], - "transformed": "a1 a2:b1 a2:b2:c1 a2:b2:c2-d1 a2:b2:c2-d2 a2:b2:c3 a2:b3 a3", + "transformed": "
a1 a2:(b1 b2:(c1 c2-(d1 d2) c3) b3) a3
", } `; @@ -126,15 +126,15 @@ exports[`transformer-variant-group > basic > "at-lg:(p-1 p-2)" 1`] = ` { "className": "at-lg:p-1", "length": 3, - "offset": 7, + "offset": 12, }, { "className": "at-lg:p-2", "length": 3, - "offset": 11, + "offset": 16, }, ], - "transformed": "at-lg:p-1 at-lg:p-2", + "transformed": "
at-lg:(p-1 p-2)
", } `; @@ -144,15 +144,15 @@ exports[`transformer-variant-group > basic > "bg-white font-light sm:hover:(bg-g { "className": "sm:hover:bg-gray-100", "length": 11, - "offset": 30, + "offset": 35, }, { "className": "sm:hover:font-medium", "length": 11, - "offset": 42, + "offset": 47, }, ], - "transformed": "bg-white font-light sm:hover:bg-gray-100 sm:hover:font-medium", + "transformed": "
bg-white font-light sm:hover:(bg-gray-100 font-medium)
", } `; @@ -162,15 +162,15 @@ exports[`transformer-variant-group > basic > "dark:lg:(p-1 p-2)" 1`] = ` { "className": "dark:lg:p-1", "length": 3, - "offset": 9, + "offset": 14, }, { "className": "dark:lg:p-2", "length": 3, - "offset": 13, + "offset": 18, }, ], - "transformed": "dark:lg:p-1 dark:lg:p-2", + "transformed": "
dark:lg:(p-1 p-2)
", } `; @@ -183,15 +183,18 @@ p-2 { "className": "!hover:m-2", "length": 4, - "offset": 8, + "offset": 13, }, { "className": "hover:p-2", "length": 3, - "offset": 14, + "offset": 19, }, ], - "transformed": "!hover:m-2 hover:p-2", + "transformed": "
hover:( +!m-2 +p-2 +)
", } `; @@ -201,15 +204,15 @@ exports[`transformer-variant-group > basic > "hover:(!m-2 p-2)" 1`] = ` { "className": "!hover:m-2", "length": 4, - "offset": 7, + "offset": 12, }, { "className": "hover:p-2", "length": 3, - "offset": 12, + "offset": 17, }, ], - "transformed": "!hover:m-2 hover:p-2", + "transformed": "
hover:(!m-2 p-2)
", } `; @@ -219,15 +222,15 @@ exports[`transformer-variant-group > basic > "lt-md:(grid grid-cols-[1fr,50%])" { "className": "lt-md:grid", "length": 4, - "offset": 7, + "offset": 12, }, { "className": "lt-md:grid-cols-[1fr,50%]", "length": 19, - "offset": 12, + "offset": 17, }, ], - "transformed": "lt-md:grid lt-md:grid-cols-[1fr,50%]", + "transformed": "
lt-md:(grid grid-cols-[1fr,50%])
", } `; @@ -237,15 +240,15 @@ exports[`transformer-variant-group > basic > "lt-sm:hover:(p-1 p-2)" 1`] = ` { "className": "lt-sm:hover:p-1", "length": 3, - "offset": 13, + "offset": 18, }, { "className": "lt-sm:hover:p-2", "length": 3, - "offset": 17, + "offset": 22, }, ], - "transformed": "lt-sm:hover:p-1 lt-sm:hover:p-2", + "transformed": "
lt-sm:hover:(p-1 p-2)
", } `; @@ -255,15 +258,15 @@ exports[`transformer-variant-group > basic > "md:(w-1/2 h-[calc(100%-4rem)])" 1` { "className": "md:w-1/2", "length": 5, - "offset": 4, + "offset": 9, }, { "className": "md:h-[calc(100%-4rem)]", "length": 19, - "offset": 10, + "offset": 15, }, ], - "transformed": "md:w-1/2 md:h-[calc(100%-4rem)]", + "transformed": "
md:(w-1/2 h-[calc(100%-4rem)])
", } `; @@ -273,15 +276,15 @@ exports[`transformer-variant-group > basic > "md:(w-40vw pr-4.5rem)" 1`] = ` { "className": "md:w-40vw", "length": 6, - "offset": 4, + "offset": 9, }, { "className": "md:pr-4.5rem", "length": 9, - "offset": 11, + "offset": 16, }, ], - "transformed": "md:w-40vw md:pr-4.5rem", + "transformed": "
md:(w-40vw pr-4.5rem)
", } `; @@ -291,14 +294,14 @@ exports[`transformer-variant-group > basic > "sm:(p-1 p-2)" 1`] = ` { "className": "sm:p-1", "length": 3, - "offset": 4, + "offset": 9, }, { "className": "sm:p-2", "length": 3, - "offset": 8, + "offset": 13, }, ], - "transformed": "sm:p-1 sm:p-2", + "transformed": "
sm:(p-1 p-2)
", } `; diff --git a/test/transformer-variant-group.test.ts b/test/transformer-variant-group.test.ts index 281e932772..7dc06c25bc 100644 --- a/test/transformer-variant-group.test.ts +++ b/test/transformer-variant-group.test.ts @@ -33,7 +33,7 @@ describe('transformer-variant-group', () => { ] for (const c of cases) { - const result = await transform(c) + const result = await transform(`
${c}
`) expect(result).toMatchSnapshot(`"${c}"`) } }) diff --git a/test/variant-group.test.ts b/test/variant-group.test.ts index 822cc0d92e..e9cd31d15d 100644 --- a/test/variant-group.test.ts +++ b/test/variant-group.test.ts @@ -3,58 +3,58 @@ import { describe, expect, it } from 'vitest' describe('variant-group', () => { it('basic', async () => { - expect(expandVariantGroup('')).toEqual('') - expect(expandVariantGroup('a b c')).toEqual('a b c') - expect(expandVariantGroup('a:b:c')).toEqual('a:b:c') - expect(expandVariantGroup('hello a:(b c) c:(a:b d)')).toEqual('hello a:b a:c c:a:b c:d') + expect(expandVariantGroup('
')).toEqual('
') + expect(expandVariantGroup('
a b c
')).toEqual('
a b c
') + expect(expandVariantGroup('
a:b:c
')).toEqual('
a:b:c
') + expect(expandVariantGroup('
hello a:(b c) c:(a:b d)
')).toEqual('
hello a:(b c) c:(a:b d)
') }) it('hoist-important', async () => { - expect(expandVariantGroup('b:c:d:(!a z)')).toEqual('!b:c:d:a b:c:d:z') + expect(expandVariantGroup('
b:c:d:(!a z)
')).toEqual('
b:c:d:(!a z)
') }) it('dash separator', async () => { - expect(expandVariantGroup('a-(b c) c-(a:b d)')).toEqual('a-b a-c c-a:b c-d') + expect(expandVariantGroup('
a-(b c) c-(a:b d)
')).toEqual('
a-(b c) c-(a:b d)
') }) it('tilde symbol', () => { - expect(expandVariantGroup('a-(~ b c)')).toEqual('a a-b a-c') + expect(expandVariantGroup('
a-(~ b c)
')).toEqual('
a-(~ b c)
') }) it('nested', () => { - expect(expandVariantGroup('a-(b c-(d e f))')).toEqual('a-b a-c-d a-c-e a-c-f') + expect(expandVariantGroup('
a-(b c-(d e f))
')).toEqual('
a-(b c-(d e f))
') }) it('spaces', () => { - expect(expandVariantGroup('a-( ~ b c )')).toEqual('a a-b a-c') + expect(expandVariantGroup('
a-( ~ b c )
')).toEqual('
a-( ~ b c )
') }) it('square bracket', async () => { - expect(expandVariantGroup('b:[&:not(c)]:d:(!a z)')).toEqual('!b:[&:not(c)]:d:a b:[&:not(c)]:d:z') + expect(expandVariantGroup('
b:[&:not(c)]:d:(!a z)
')).toEqual('
b:[&:not(c)]:d:(!a z)
') }) it('square bracket case2', async () => { - expect(expandVariantGroup('[&]:(a-b c-d)')).toEqual('[&]:a-b [&]:c-d') + expect(expandVariantGroup('
[&]:(a-b c-d)
')).toEqual('
[&]:(a-b c-d)
') }) it('expand with space', async () => { const shortcut = ' a:(b:(c-d d-c)) ' - expect(expandVariantGroup(shortcut)).toEqual(' a:b:c-d a:b:d-c ') + expect(expandVariantGroup(shortcut)).toEqual(' a:(b:(c-d d-c)) ') expect(expandVariantGroup(shortcut.trim()).split(/\s+/g)).toMatchInlineSnapshot(` [ - "a:b:c-d", - "a:b:d-c", + "a:(b:(c-d", + "d-c))", ] `) }) it('expand @', async () => { - expect(expandVariantGroup('@a:(c-d d-c)')).toEqual('@a:c-d @a:d-c') - expect(expandVariantGroup('!@a:(c-d d-c)')).toEqual('!@a:c-d !@a:d-c') + expect(expandVariantGroup('
@a:(c-d d-c)
')).toEqual('
@a:(c-d d-c)
') + expect(expandVariantGroup('
!@a:(c-d d-c)
')).toEqual('
!@a:(c-d d-c)
') }) - it('inlucde ?', async () => { - expect(expandVariantGroup('a:(b?c d)')).toEqual('a:b?c a:d') + it('include ?', async () => { + expect(expandVariantGroup('
a:(b?c d)
')).toEqual('
a:(b?c d)
') }) })