UNPKG

@silexlabs/grapesjs-advanced-selector

Version:

A GrapesJS plugin for managing advanced CSS selectors with a visual interface

233 lines (203 loc) 7 kB
import { CompoundSelector, toString, specificity, fromString } from './CompoundSelector' import { SimpleSelectorType, SimpleSelector, TagSelector, IdSelector, ClassSelector, AttributeSelector, UniversalSelector } from './SimpleSelector' import { PseudoClass } from './PseudoClass' type AnySelector = TagSelector | IdSelector | ClassSelector | AttributeSelector | UniversalSelector const tagSelector: TagSelector = { type: SimpleSelectorType.TAG, value: 'div', active: true } const IdSelector: IdSelector = { type: SimpleSelectorType.ID, value: 'main', active: true } const classSelector: ClassSelector = { type: SimpleSelectorType.CLASS, value: 'container', active: true } const attributeSelector: AttributeSelector = { type: SimpleSelectorType.ATTRIBUTE, value: 'data-test', active: true } const universalSelector: UniversalSelector = { type: SimpleSelectorType.UNIVERSAL, active: true } const pseudoClass = { type: 'hover', hasParam: false, sentencePre: 'prefix ', } // Mock functions from SimpleSelector and PseudoClass jest.mock('./SimpleSelector', () => ({ ...jest.requireActual('./SimpleSelector'), toString: (selector: AnySelector) => { switch (selector.type) { case 'id': return `#${selector.value}` case 'class': return `.${selector.value}` case 'tag': return selector.value case 'attribute': return `[${selector.value}]` case 'universal': return '*' default: return '' } }, specificity: (selector: SimpleSelector) => { switch (selector.type) { case 'id': return 100 case 'class': return 10 case 'tag': return 1 case 'attribute': return 10 case 'universal': return 0 default: return 0 } } })) jest.mock('./PseudoClass', () => ({ toString: (pseudoClass: PseudoClass) => `:${pseudoClass.type}`, PSEUDO_CLASSES: jest.requireActual('./PseudoClass').PSEUDO_CLASSES, })) describe('CompoundSelector', () => { test('toString - builds correct selector string', () => { expect(toString({ selectors: [ tagSelector, classSelector, IdSelector, ] } as CompoundSelector)).toBe('div#main.container') expect(toString({ selectors: [ classSelector, ], pseudoClass, } as CompoundSelector)).toBe('.container:hover') expect(toString({ selectors: [ tagSelector, { ...IdSelector, active: false, }, classSelector, ] })).toBe('div.container') expect(toString({ selectors: [ IdSelector, classSelector, ] })).toBe('#main.container') expect(toString({ selectors: [ classSelector, IdSelector, ] })).toBe('#main.container') expect(toString({ selectors: [ classSelector, tagSelector, IdSelector, ] })).toBe('div#main.container') expect(toString({ selectors: [ universalSelector, classSelector, tagSelector, IdSelector, ] })).toBe('*') expect(toString({ selectors: [ attributeSelector, classSelector, tagSelector, IdSelector, ] })).toBe('div#main.container[data-test]') }) test('toString - bare pseudo-class without active selectors returns empty string', () => { expect(toString({ selectors: [], pseudoClass, } as CompoundSelector)).toBe('') expect(toString({ selectors: [ { ...classSelector, active: false }, ], pseudoClass, } as CompoundSelector)).toBe('') }) test('specificity - calculates correct specificity', () => { expect(specificity({ selectors: [ IdSelector, classSelector, tagSelector, ] })).toBe(100 + 10 + 1) // ID (100) + Class (10) + Tag (1) expect(specificity({ selectors: [ classSelector, ], pseudoClass, } as CompoundSelector)).toBe(10 + 10) // Class (10) + Pseudo-Class (10) expect(specificity({ selectors: [ { ...classSelector, active: false, }, classSelector, ] })).toBe(10) // Only the active class contributes }) }) describe('parseSelectorString', () => { test('parses a tag selector', () => { const result = fromString('div') expect(result.selectors).toEqual([{ type: SimpleSelectorType.TAG, value: 'div', active: true }]) }) test('parses an ID selector', () => { const result = fromString('#main') expect(result.selectors).toEqual([{ type: SimpleSelectorType.ID, value: 'main', active: true }]) }) test('parses a class selector', () => { const result = fromString('.container') expect(result.selectors).toEqual([{ type: SimpleSelectorType.CLASS, value: 'container', active: true }]) }) test('parses a universal selector', () => { const result = fromString('*') expect(result.selectors).toEqual([{ type: SimpleSelectorType.UNIVERSAL, active: true }]) }) test('parses an attribute selector', () => { const result = fromString('[data-role=button]') expect(result.selectors).toEqual([ { type: SimpleSelectorType.ATTRIBUTE, value: 'data-role', operator: '=', attributeValue: 'button', active: true }, ]) }) test('parses an attribute selector with quotes', () => { const result = fromString('[data-id="123"]') expect(result.selectors).toEqual([ { type: SimpleSelectorType.ATTRIBUTE, value: 'data-id', operator: '=', attributeValue: '123', active: true }, ]) }) test('parses a tag with a class', () => { const result = fromString('button.primary') expect(result.selectors).toEqual([ { type: SimpleSelectorType.TAG, value: 'button', active: true }, { type: SimpleSelectorType.CLASS, value: 'primary', active: true }, ]) }) test('parses a tag with an ID', () => { const result = fromString('nav#main-menu') expect(result.selectors).toEqual([ { type: SimpleSelectorType.TAG, value: 'nav', active: true }, { type: SimpleSelectorType.ID, value: 'main-menu', active: true }, ]) }) test('parses a selector with a pseudo-class', () => { const result = fromString('a:hover') expect(result.selectors).toEqual([{ type: SimpleSelectorType.TAG, value: 'a', active: true }]) expect(result.pseudoClass?.type).toBe('hover') }) test('parses a selector with multiple classes and a pseudo-class', () => { const result = fromString('.button.large:focus') expect(result.selectors).toEqual([ { type: SimpleSelectorType.CLASS, value: 'button', active: true }, { type: SimpleSelectorType.CLASS, value: 'large', active: true }, ]) expect(result.pseudoClass?.type).toBe('focus') }) test('parses a selector with a pseudo-class with params', () => { const result = fromString(':nth-child(2)') expect(result.selectors).toEqual([]) expect(result.pseudoClass?.type).toBe('nth-child') expect(result.pseudoClass?.param).toBe('2') }) })