UNPKG

@riovir/wc-fontawesome

Version:

Web components for Font Awesome

259 lines (221 loc) 9.34 kB
import { describe, test, expect, vi } from 'vitest'; import { attributeAsProp, setAttribute } from '#test/utils'; import { faCoffee, faDuotone, faOtherDuotone, pathOf, viewBoxOf } from '#test/fixtures/icons'; import { FontAwesomeIcon, Props } from './font-awesome-icon'; const TAG = 'fa-icon'; customElements['define'](TAG, FontAwesomeIcon); describe('icon', () => { const examples = ['coffee', 'duotone']; testAttributeAsDefault({ prop: 'icon', examples }); testAttributeNotSynced({ prop: 'icon', examples }); test('left undefined renders as blank SVG', () => { const { svg, path } = setup(); expect(svg().getAttribute('viewBox')).toBe('0 0 1 1'); expect(path().getAttribute('d')).toBe(''); }); test('setting prop updates SVG viewBox', () => { const { host, svg, path } = setup(); host.icon = faCoffee; expect(svg().getAttribute('viewBox')).toBe(viewBoxOf(faCoffee)); expect(path().getAttribute('d')).toBe(pathOf(faCoffee)); }); test('switches between mono- and duo-tone SVG structure when changed', () => { const { host, pathSecondary } = setup(); host.icon = faDuotone; expect(pathSecondary()).toBeTruthy(); host.icon = faCoffee; expect(pathSecondary()).not.toBeTruthy(); }); /** The NVDA screen reader on Firefox announced the icon as "blank" in the past. We set role "presentation" when no role or aria-label * are set to ensure decorative icons are skipped by screen readers. */ describe('aria attributes', () => { test('ensures role: presentation when no role, aria-label, aria-labelled, adn title is set', () => { const { host } = setup(); host.connectedCallback(); expect(host.getAttribute('role')).toBe('presentation'); expect(host.hasAttribute('aria-label')).toBeFalsy(); expect(host.hasAttribute('aria-labelledby')).toBeFalsy(); expect(host.hasAttribute('title')).toBeFalsy(); }); test('does not set role to presentation when role is already set', () => { const { host } = setup(); host.setAttribute('role', 'some-role'); host.connectedCallback(); expect(host.getAttribute('role')).toBe('some-role'); }); test('does not set role when aria-label is set', () => { const { host } = setup(); host.setAttribute('aria-label', 'alt text'); host.connectedCallback(); expect(host.hasAttribute('role')).toBeFalsy(); }); test('does not set role when aria-labelledby is set', () => { const { host } = setup(); host.setAttribute('aria-labelledby', 'some-id'); host.connectedCallback(); expect(host.hasAttribute('role')).toBeFalsy(); }); test('does not set role when title is set', () => { const { host } = setup(); host.setAttribute('title', 'alt text'); host.connectedCallback(); expect(host.hasAttribute('role')).toBeFalsy(); }); }); }); describe('icon set to duotone definition', () => { test('renders primary and secondary paths', () => { const { host, svg, pathPrimary, pathSecondary } = setup(); host.icon = faDuotone; expect(svg().getAttribute('viewBox')).toBe(viewBoxOf(faDuotone)); const [primary, secondary] = pathOf(faDuotone); expect(pathPrimary().getAttribute('d')).toBe(primary); expect(pathSecondary().getAttribute('d')).toBe(secondary); }); test('updates primary and secondary on icon change', () => { const { host, svg } = setup(); host.icon = faDuotone; svg().setAttribute('tagged-by-test', ''); host.icon = faOtherDuotone; expect(svg().getAttribute('viewBox')).toBe(viewBoxOf(faOtherDuotone)); expect(svg().hasAttribute('tagged-by-test')).toBeTruthy(); }); }); describe('transform', () => { const examples = ['rotate-30', 'rotate-45']; testAttributeAsDefault({ prop: 'transform', examples }); testAttributeNotSynced({ prop: 'transform', examples }); }); describe('size', () => { const examples = ['2x', '3x']; testAttributeAsDefault({ prop: 'size', examples }); testAttributeNotSynced({ prop: 'size', examples }); }); describe('rotateBy', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'rotateBy', attribute: 'rotate-by', examples }); testAttributeNotSynced({ prop: 'rotateBy', attribute: 'rotate-by', examples }); testTogglingClass({ prop: 'rotateBy', faClass: 'fa-rotate-by' }); }); describe('rotation', () => { const examples = ['90', '180']; testAttributeAsDefault({ prop: 'rotation', examples }); testAttributeNotSynced({ prop: 'rotation', examples }); }); describe('flip', () => { const examples = ['horizontally', 'vertically']; testAttributeAsDefault({ prop: 'flip', examples }); testAttributeNotSynced({ prop: 'flip', examples }); }); describe('pull', () => { const examples = ['left', 'right']; testAttributeAsDefault({ prop: 'pull', examples }); test('attribute updates prop', () => { const { host } = setup(); host.pull = 'left'; setAttribute(host, 'pull', 'right'); Props.pull.connect(host, 'pull'); expect(host.pull).toBe('right'); }); test('prop change updates attribute', () => { const host = document.createElement(TAG); Props.pull.observe(host, 'left'); expect(host.getAttribute('pull')).toBe('left'); }); }); describe('mask', () => { const examples = ['coffee', 'comment']; testAttributeAsDefault({ prop: 'mask', examples }); testAttributeNotSynced({ prop: 'mask', examples }); }); describe('fixedWidth', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'fixedWidth', attribute: 'fixed-width', examples }); testAttributeNotSynced({ prop: 'fixedWidth', attribute: 'fixed-width', examples }); testTogglingClass({ prop: 'fixedWidth', faClass: 'fa-fw' }); }); describe('widthAuto', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'widthAuto', attribute: 'width-auto', examples }); testAttributeNotSynced({ prop: 'widthAuto', attribute: 'width-auto', examples }); testTogglingClass({ prop: 'widthAuto', faClass: 'fa-width-auto' }); }); describe('spin', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'spin', examples }); testAttributeNotSynced({ prop: 'spin', examples }); testTogglingClass({ prop: 'spin' }); }); describe('pulse', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'pulse', examples }); testAttributeNotSynced({ prop: 'pulse', examples }); testTogglingClass({ prop: 'pulse' }); }); describe('inverse', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'inverse', examples }); testAttributeNotSynced({ prop: 'inverse', examples }); testTogglingClass({ prop: 'inverse' }); }); describe('swapOpacity', () => { const examples = [true, false]; testAttributeAsDefault({ prop: 'swapOpacity', attribute: 'swap-opacity', examples }); testAttributeNotSynced({ prop: 'swapOpacity', attribute: 'swap-opacity', examples }); testTogglingClass({ prop: 'swapOpacity', faClass: 'fa-swap-opacity' }); }); function testAttributeAsDefault({ prop, attribute = prop, examples = [], example = examples[0] }) { const message = prop === attribute ? 'attribute initializes undefined prop' : `attribute "${attribute}" initializes undefined prop`; test(message, () => { const { host } = setup(); setAttribute(host, attribute, example); Props[prop].connect(host, prop); expect(host[prop]).toBe(example); }); } /** Due to performance reasons attributes are only meant to initialize undefined props. */ function testAttributeNotSynced({ prop, attribute = prop, examples = [] }) { const [example1, example2] = examples; const message = prop === attribute ? 'attribute is not synced with prop' : `attribute "${attribute}" is not synced with prop`; test(message, () => { const { host } = setup(); attributeResetsPropOnConnect({ prop, attribute, examples }); setAttribute(host, attribute, example1); if (Props[prop].observe) { Props[prop].observe(host, example2, example1); expect(attributeAsProp(host, attribute)).toBe(example1); } }); } function testTogglingClass({ prop, faClass = `fa-${prop}` }) { test(`${prop} toggles ${faClass} class in SVG`, () => { const { host, svg } = setup(); host[prop] = true; expect(svg().classList.contains(faClass)).toBeTruthy(); host[prop] = false; expect(svg().classList.contains(faClass)).toBeFalsy(); }); } function attributeResetsPropOnConnect({ prop, attribute = prop, examples: [example1, example2] }) { const { host } = setup(); setAttribute(host, attribute, example1); host[prop] = example2; Props[prop].connect(host, prop); expect(host[prop]).toBe(example1); } function setup() { const host = document.createElement(TAG); const definitions = { 'coffee': faCoffee, 'duotone': faDuotone }; host._resolve = icon => definitions[icon] || icon; const parseTransform = vi.fn(); host._parseTransform = parseTransform; const svg = () => host.shadowRoot.querySelector('svg'); const paths = () => [...host.shadowRoot.querySelectorAll('path')]; const pathPrimary = () => 1 < paths().length ? paths()[1] : paths()[0]; const pathSecondary = () => 1 < paths().length ? paths()[0] : undefined; return { host, svg, path: pathPrimary, pathPrimary, pathSecondary, parseTransform }; }