Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MWPW-160510 - Expand icon tooltip functionality #3173

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 11 additions & 20 deletions libs/features/icons/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,6 @@ export const fetchIcons = (config) => new Promise(async (resolve) => {
resolve(fetchedIcons);
});

async function decorateToolTip(icon) {
const wrapper = icon.closest('em');
if (!wrapper) return;
wrapper.className = 'tooltip-wrapper';
const conf = wrapper.textContent.split('|');
// Text is the last part of a tooltip
const content = conf.pop().trim();
if (!content) return;
icon.dataset.tooltip = content;
// Position is the next to last part of a tooltip
const place = conf.pop()?.trim().toLowerCase() || 'right';
const defaultIcon = 'info-outline';
icon.className = `icon icon-${defaultIcon} milo-tooltip ${place}`;
icon.dataset.name = defaultIcon;
wrapper.parentElement.replaceChild(icon, wrapper);
}

export function getIconData(icon) {
const fedRoot = getFederatedContentRoot();
const name = [...icon.classList].find((c) => c.startsWith('icon-'))?.substring(5);
Expand Down Expand Up @@ -87,8 +70,15 @@ function filterDuplicatedIcons(icons) {
return uniqueIcons;
}

export async function decorateIcons(area, icons, config) {
export function handleLegacyToolTip(icons, iconClass = 'icon-info-outline') {
const tooltips = [...icons].filter((icon) => icon.classList.contains('icon-tooltip'));
if (!tooltips.length) return;
tooltips.forEach((icon) => icon.classList.replace('icon-tooltip', iconClass));
}

export async function decorateIcons(icons, config) {
if (!icons.length) return;
handleLegacyToolTip(icons);
const uniqueIcons = filterDuplicatedIcons(icons);
if (!uniqueIcons.length) return;
preloadInViewIcons(uniqueIcons);
Expand All @@ -106,8 +96,9 @@ export default async function loadIcons(icons) {
const iconsToFetch = new Map();

icons.forEach(async (icon) => {
const isToolTip = icon.classList.contains('icon-tooltip');
if (isToolTip) decorateToolTip(icon);
if (icon.dataset.tooltip) {
icon.classList.add('milo-tooltip', icon.dataset.tooltipdir);
}
const iconName = icon.dataset.name;
if (icon.dataset.svgInjected || !iconName) return;
if (!federalIcons[iconName] && !iconsToFetch.has(iconName)) {
Expand Down
21 changes: 15 additions & 6 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -793,9 +793,11 @@ const findReplaceableNodes = (area) => {
let matchFound = false;
if (node.nodeType === Node.TEXT_NODE) {
matchFound = regex.test(node.nodeValue);
} else if (node.nodeType === Node.ELEMENT_NODE && node.hasAttribute('href')) {
const hrefValue = node.getAttribute('href');
matchFound = regex.test(hrefValue);
} else if (node.nodeType === Node.ELEMENT_NODE) {
const attr = node.getAttribute('href') || node.getAttribute('data-tooltip');
if (attr) {
matchFound = regex.test(attr);
}
}
if (matchFound) {
nodes.push(node);
Expand Down Expand Up @@ -1250,8 +1252,15 @@ async function resolveInlineFrags(section) {
section.preloadLinks = newlyDecoratedSection.preloadLinks;
}

export function setIconsIndexClass(icons) {
export function setIconAttrs(icons) {
[...icons].forEach((icon) => {
const em = icon.closest('em');
const conf = em?.textContent.split('|');
if (em && conf.length) {
icon.dataset.tooltip = conf?.pop()?.trim();
icon.dataset.tooltipdir = conf?.pop()?.trim().toLowerCase() || 'right';
em.parentElement.replaceChild(icon, em);
}
const parent = icon.parentNode;
const children = parent.childNodes;
const nodeIndex = [...children].indexOf.call(children, icon);
Expand Down Expand Up @@ -1303,7 +1312,7 @@ export async function loadArea(area = document) {

const allIcons = area.querySelectorAll('span.icon');
if (allIcons.length) {
setIconsIndexClass(allIcons);
setIconAttrs(allIcons);
}

const sections = decorateSections(area, isDoc);
Expand All @@ -1320,7 +1329,7 @@ export async function loadArea(area = document) {

if (allIcons.length) {
const { default: loadIcons, decorateIcons } = await import('../features/icons/icons.js');
await decorateIcons(area, allIcons, config);
await decorateIcons(allIcons, config);
await loadIcons(allIcons);
}

Expand Down
31 changes: 15 additions & 16 deletions test/features/icons/icons.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { readFile } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';
import sinon, { stub } from 'sinon';
import { stub } from 'sinon';
import { waitForElement } from '../../helpers/waitfor.js';

const { default: loadIcons, getIconData } = await import('../../../libs/features/icons/icons.js');
const { setIconsIndexClass } = await import('../../../libs/utils/utils.js');
const { default: loadIcons, getIconData, handleLegacyToolTip } = await import('../../../libs/features/icons/icons.js');
const { setIconAttrs } = await import('../../../libs/utils/utils.js');
const mockRes = ({ payload, status = 200, ok = true } = {}) => new Promise((resolve) => {
resolve({
status,
Expand All @@ -22,33 +22,32 @@ const svgEx = `<?xml version="1.0" encoding="UTF-8"?>
<path fill="currentcolor" d="M12,10V1.5a.5.5,0,0,0-.5-.5h-5a.5.5,0,0,0-.5.5V10H2.5035a.25.25,0,0,0-.177.427L9,17.1l6.673-6.673A.25.25,0,0,0,15.4965,10Z"></path>
</svg>`;

describe('Icon Suppprt', () => {
beforeEach(() => {
stub(window, 'fetch').callsFake(() => mockRes({}));
});

afterEach(() => {
sinon.restore();
});

it('Replaces span.icon', async () => {
describe('Icon Support', () => {
let fetchStub;
before(async () => {
const payload = svgEx;
window.fetch.returns(mockRes({ payload }));

fetchStub = stub(window, 'fetch').callsFake(() => mockRes({ payload }));
icons = document.querySelectorAll('span.icon');
setIconAttrs(icons);
handleLegacyToolTip(icons, 'icon-play');
icons.forEach((icon) => {
const { name } = getIconData(icon);
icon.dataset.name = name;
});
await loadIcons(icons);
});

after(() => {
fetchStub.restore();
});

it('Replaces span.icon', async () => {
const selector = await waitForElement('span.icon svg');
expect(selector).to.exist;
});

it('Sets icon index class', async () => {
icons = document.querySelectorAll('span.icon');
setIconsIndexClass(icons);
const secondIconHasIndexClass = icons[2].classList.contains('node-index-last');
expect(secondIconHasIndexClass).to.be.true;
});
Expand Down
1 change: 1 addition & 0 deletions test/features/icons/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<p>This is text w/ an <span class="icon icon-play"></span> in the middle of the paragraph.</p>
<p><em><span class="icon icon-tooltip"></span> | This is my tooltip text.</em></p>
<p><em><span class="icon icon-tooltip"></span> | top | This is my tooltip text.</em></p>
<p><em><span class="icon icon-play"></span>| left | This is my tooltip text.</em></p>
</div>
</main>
<footer></footer>
Loading