@riovir/wc-fontawesome
Version:
Web components for Font Awesome
244 lines (208 loc) • 8.66 kB
JavaScript
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')).toBe(true);
});
});
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('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('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)).toBe(true);
host[prop] = false;
expect(svg().classList.contains(faClass)).toBe(false);
});
}
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 = jest.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 };
}