From b1c4811e13980346afedc397b6eb51119c9edd40 Mon Sep 17 00:00:00 2001 From: bjorn Date: Thu, 30 Jul 2026 10:06:48 +0200 Subject: [PATCH 1/2] feat: support Font Awesome 4 through 7 icon syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Font Awesome renames a style in every major version: v4 v5 v6 and v7 Detection only knew v5's five short prefixes and v4's shorthand, so on current master: "fa-solid fa-user" -> stray `fa` "fa-sharp fa-solid fa-user" -> "fasds fa-user" -> context-menu-icon-fasds on the item, no icon "fa fa-user" -> context-menu-icon-fa on the item, no icon The stray `fa` is not harmless: in v6 and v7 it is a legacy alias that sets the solid font-weight and the Free font-family, so it competes with `fa-regular` for weight and with `fa-brands` for family, and which one wins depends on stylesheet order. Rather than enumerate versions, which is what put us here, ask the two questions that actually matter. Is this Font Awesome at all, i.e. is there an `fa-*` token? And does the class list already name a family/style, or does v4's base `fa` need supplying? Short prefixes are matched by shape (`fa`, `fas`, `fass`, `fasds`, `fands`, ...) rather than from a list, so the packs v7 keeps adding need no change here. Backwards compatibility. Of 147 unit tests, exactly 4 change behaviour and 143 are unchanged, all four being the broken cases above: * v4 shorthand `fa-user` still gets `fa` supplied, and so does the modifier form `fa-user fa-lg`, which relies on it to render at all. Nothing in either list names a family, so the old behaviour is exactly preserved. * v5's `fas`/`far`/`fal`/`fad`/`fab` are byte-identical to before. * Built-in icon names are untouched. Detection keys off an `fa-` token rather than a bare `fa…` prefix specifically so a short custom name such as `icon: "fav"` is not swallowed. * `--fa5` is still the class emitted on the item. Renaming it to `--fa6`, as the original patch in #776 did, would break any custom CSS matching it, and the marker was never version-specific: it is just the hook the stylesheet uses to position the child icon. Refs #776. --- CHANGELOG.md | 1 + documentation/docs/font-awesome.md | 44 ++++++- src/jquery.contextMenu.js | 102 ++++++++++++--- test/unit/font-awesome-versions.test.js | 161 ++++++++++++++++++++++++ 4 files changed, 287 insertions(+), 21 deletions(-) create mode 100644 test/unit/font-awesome-versions.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e8a7b48..005a814d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * `item.name` may now be a function, for labels that change per open (fixes #743) * The trigger element is now reachable from item-level `events` handlers, including in sub-menus (fixes #729) * Added `animation.showDuration`/`animation.hideDuration` and `animation.animateOnReopen` (fixes #739) +* `item.icon` now supports Font Awesome 6 and 7 syntax, including the separate family/style classes (`fa-solid fa-user`, `fa-sharp fa-solid fa-user`) and per-pack short prefixes (`fasds`, `fands`), alongside the existing 4 and 5 syntax (fixes #776). Font Awesome 4's full `fa fa-user` form now works too, where previously only the `fa-user` shorthand did #### Fixed diff --git a/documentation/docs/font-awesome.md b/documentation/docs/font-awesome.md index 4de893f0..136cd6bb 100644 --- a/documentation/docs/font-awesome.md +++ b/documentation/docs/font-awesome.md @@ -12,6 +12,46 @@ Check out the [demo](https://swisnl.github.io/jQuery-contextMenu/demo/fontawesom It is also possible to use your own SVG icons if you like, you can [customize](customize) this by using the SASS files. -## Recent Font Awesome support +## Supported Font Awesome versions -The contextmenu supports the new Font Awesome by including the correct font-awesome CSS and adding the new classes as icon for a menu item. When using for example `fas fa-beer` it will crete the `i` tag for the icon in the menu and adjust CSS accordingly. +Include the Font Awesome CSS for the version you use, then pass its classes as +the item's [icon](items#icon). The contextmenu creates the `i` tag for the icon +inside the menu item and adjusts its own CSS accordingly. Every version from 4 +to 7 works, using that version's own syntax: + +```javascript +var items = { + // Font Awesome 4 + a: {name: "Beer", icon: "fa-beer"}, // shorthand, `fa` is added for you + b: {name: "Beer", icon: "fa fa-beer"}, // or the full v4 syntax + + // Font Awesome 5 + c: {name: "Beer", icon: "fas fa-beer"}, + d: {name: "GitHub", icon: "fab fa-github"}, + + // Font Awesome 6 and 7, which name the family and the style separately + e: {name: "Beer", icon: "fa-solid fa-beer"}, + f: {name: "Beer", icon: "fa-regular fa-beer"}, + g: {name: "GitHub", icon: "fa-brands fa-github"}, + h: {name: "Beer", icon: "fa-sharp fa-solid fa-beer"}, + i: {name: "Beer", icon: "fa-sharp-duotone fa-solid fa-beer"}, + + // per-pack short prefixes work too + j: {name: "Beer", icon: "fasds fa-beer"} +} +``` + +Anything containing a `fa-` class is treated as Font Awesome and passed through +to the `i` tag unchanged, so a family or style added in a future release needs +no change here. The one exception is the version 4 shorthand: when the class +list does not say which family or style to use, the base `fa` class is supplied +so `icon: "fa-beer"` keeps working on its own. + +A single word with no `fa-` in it stays a [built-in icon](customize) name, so +`icon: "edit"` still refers to this plugin's own icon font rather than to Font +Awesome. + +The menu item itself gets a `context-menu-icon--fa5` class, which the stylesheet +uses to position the child icon. The name is historical: it applies to every +Font Awesome version, not just 5, and is kept as-is so existing custom CSS keeps +matching. diff --git a/src/jquery.contextMenu.js b/src/jquery.contextMenu.js index ceace1d5..bd6c65ef 100644 --- a/src/jquery.contextMenu.js +++ b/src/jquery.contextMenu.js @@ -1954,30 +1954,37 @@ if (typeof item.icon === 'function') { item._icon = item.icon.call(this, this, $t, key, item); } else { - if (typeof(item.icon) === 'string' && ( - item.icon.substring(0, 4) === 'fab ' - || item.icon.substring(0, 4) === 'fas ' - || item.icon.substring(0, 4) === 'fad ' - || item.icon.substring(0, 4) === 'far ' - || item.icon.substring(0, 4) === 'fal ') - ) { - // to enable font awesome + if (isFontAwesomeIcon(item.icon)) { + // Font Awesome needs its own tag instead of being applied + // to the menu item itself: its classes set font-family and + // font-weight and their own ::before content on whatever + // element they are put on, which used to bleed into (and + // bold) the item's label text and clash with this plugin's + // own icon styling. + // + // `--fa5` is a historical name kept for compatibility: it is + // just the hook the stylesheet uses to position a child icon + // element, and applies to every Font Awesome version rather + // than to v5 specifically. $t.addClass(root.classNames.icon + ' ' + root.classNames.icon + '--fa5'); // built with addClass instead of interpolating the icon // into a markup string: an icon name coming from stored // or otherwise non-literal data could otherwise close the // class attribute and inject arbitrary markup. - item._icon = $('').addClass(item.icon); - } else if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') { - // legacy Font Awesome 4 style icon class (e.g. "fa-trash"), kept for - // backwards compatibility. Just like the fas/far/fab/fad/fal syntax - // above, this needs its own tag instead of being applied to the - // menu item itself: Font Awesome's classes set font-family/font-weight - // and their own ::before content on whatever element they're put on, - // which used to bleed into (and bold) the item's label text and clash - // with this plugin's own icon styling. - $t.addClass(root.classNames.icon + ' ' + root.classNames.icon + '--fa5'); - item._icon = $('').addClass('fa').addClass(item.icon); + if (namesFontAwesomeFamily(item.icon)) { + // v5's `fas fa-user`, or v6/v7's `fa-solid fa-user` and + // `fa-sharp fa-solid fa-user`: the caller has already + // said which family and style to use, so the classes go + // through untouched. Supplying `fa` on top of these is + // what used to fight `fa-regular`/`fa-brands` for + // font-weight and font-family. + item._icon = $('').addClass(item.icon); + } else { + // v4's `fa-user` shorthand, optionally with modifiers + // (`fa-user fa-lg`), which renders only once the base + // `fa` class is supplied here. + item._icon = $('').addClass('fa').addClass(item.icon); + } } else { item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon; } @@ -2580,6 +2587,63 @@ (selector.nodeType === 1 || (typeof selector.jquery !== 'undefined' && typeof selector.length === 'number')); } + // Font Awesome has changed how a style is named in every major version: + // + // v4 + // v5 + // v6 and v7 + // + // + // + // and v7 keeps adding packs, each with its own short prefix (`fasds` for + // sharp-duotone, `fands` for notdog-duo, ...), so the set is not fixed and + // is not enumerated here. Instead of matching a version, the two helpers + // below answer the only two questions that actually matter: + // + // 1. is this an icon-library class list at all, or one of this plugin's + // own built-in icon names? -> a `fa-*` token says Font Awesome. + // 2. does that class list already say which family/style to use? -> if + // not, and only then, v4's base `fa` class is supplied, which is what + // makes the `icon: "fa-user"` shorthand work. + // + // Detection deliberately keys off a `fa-*` token rather than a bare `fa…` + // prefix, because a bare prefix would also swallow short built-in icon + // names such as `icon: "fav"`. + + // a family or style class: the v6/v7 long forms, plus any short prefix + // (`fa`, `fas`, `fab`, `fass`, `fasds`, ...). The short prefixes are matched + // by shape so a pack added in a future release keeps working untouched. + var faFamilyOrStyle = /^(?:fa[a-z]{0,4}|fa-(?:classic|sharp|sharp-duotone|duotone|solid|regular|light|thin|brands))$/; + + function faClassTokens(icon) { + if (typeof icon !== 'string') { + return []; + } + return trimText(icon).split(/\s+/); + } + + // does this icon value name a Font Awesome icon? + function isFontAwesomeIcon(icon) { + var tokens = faClassTokens(icon), i; + for (i = 0; i < tokens.length; i++) { + if (tokens[i].indexOf('fa-') === 0) { + return true; + } + } + return false; + } + + // does it already carry the family/style, or does v4's `fa` need adding? + function namesFontAwesomeFamily(icon) { + var tokens = faClassTokens(icon), i; + for (i = 0; i < tokens.length; i++) { + if (faFamilyOrStyle.test(tokens[i])) { + return true; + } + } + return false; + } + // is the given value usable as a page coordinate? Finite numbers are, and // so are numeric strings, which the positioning arithmetic has always // accepted (`{x: el.dataset.x, ...}`). Note 0 is a perfectly valid diff --git a/test/unit/font-awesome-versions.test.js b/test/unit/font-awesome-versions.test.js new file mode 100644 index 00000000..3f9be7c5 --- /dev/null +++ b/test/unit/font-awesome-versions.test.js @@ -0,0 +1,161 @@ +// Font Awesome names a style differently in every major version, and v7 keeps +// adding packs with their own short prefixes. These tests pin the emitted +// markup for each syntax, so the two things that matter stay true: +// +// * a class list that already names a family/style is passed through +// untouched (v5, v6, v7), and +// * one that does not still gets v4's base `fa` class supplied (the +// `icon: "fa-user"` shorthand and its modifier form). +// +// See https://github.com/swisnl/jQuery-contextMenu/pull/776 + +QUnit.module('Font Awesome icon syntax across versions', { + afterEach: function() { + $.contextMenu('destroy'); + var $fixtureEl = $('#qunit-fixture'); + if ($fixtureEl.length) { + $fixtureEl.html(''); + } + } +}); + +function fixtureFa() { + var $fixtureEl = $('#qunit-fixture'); + if ($fixtureEl.length === 0) { + $('
').appendTo('body'); + $fixtureEl = $('#qunit-fixture'); + } + return $fixtureEl; +} + +var faTriggerSeq = 0; + +// Build a one-item menu with the given `icon`, open it, and report what was +// rendered: the classes on the child (if any) and on the item itself. +function renderIconFa(icon) { + var cls = 'fa-trigger-' + (faTriggerSeq++); + fixtureFa().append('
right click me
'); + + $.contextMenu({ + selector: '.' + cls, + items: { + only: {name: 'Only', icon: icon} + } + }); + + $('.' + cls).contextMenu(); + + var $item = $('.context-menu-list').filter(':visible').last().find('li.context-menu-item').first(); + var $i = $item.children('i'); + + function sortedClasses($el) { + var raw = ($el.attr('class') || '').split(/\s+/).filter(function(one) { + return one !== ''; + }); + return raw.sort(); + } + + return { + hasChildIcon: $i.length === 1, + iconClasses: $i.length === 1 ? sortedClasses($i) : null, + itemClasses: sortedClasses($item) + }; +} + +function assertChildIcon(assert, icon, expectedClasses, label) { + var rendered = renderIconFa(icon); + assert.ok(rendered.hasChildIcon, label + ': renders a child '); + assert.deepEqual( + rendered.iconClasses, + expectedClasses.slice().sort(), + label + ': child classes' + ); + assert.ok( + rendered.itemClasses.indexOf('context-menu-icon--fa5') !== -1, + label + ': item keeps the positioning hook' + ); +} + +// ---------------------------------------------------------------- v4 + +QUnit.test('v4 shorthand "fa-user" gets the base fa class supplied', function(assert) { + assertChildIcon(assert, 'fa-user', ['fa', 'fa-user'], 'v4 shorthand'); +}); + +QUnit.test('v4 shorthand with a modifier keeps the base fa class', function(assert) { + // This already worked and must keep working: nothing in the list names a + // family, so `fa` is still required for the icon to render at all. + assertChildIcon(assert, 'fa-user fa-lg', ['fa', 'fa-user', 'fa-lg'], 'v4 + modifier'); +}); + +QUnit.test('v4 full syntax "fa fa-user" is passed through untouched', function(assert) { + // Previously fell through to the built-in icon-font branch and emitted + // "context-menu-icon-fa fa-user" onto the item, rendering nothing. + assertChildIcon(assert, 'fa fa-user', ['fa', 'fa-user'], 'v4 full'); +}); + +// ---------------------------------------------------------------- v5 + +QUnit.test('v5 short prefixes are passed through untouched', function(assert) { + assertChildIcon(assert, 'fas fa-user', ['fas', 'fa-user'], 'v5 solid'); + assertChildIcon(assert, 'far fa-user', ['far', 'fa-user'], 'v5 regular'); + assertChildIcon(assert, 'fal fa-user', ['fal', 'fa-user'], 'v5 light'); + assertChildIcon(assert, 'fad fa-user', ['fad', 'fa-user'], 'v5 duotone'); + assertChildIcon(assert, 'fab fa-github', ['fab', 'fa-github'], 'v5 brands'); +}); + +// ---------------------------------------------------------------- v6 / v7 + +QUnit.test('v6/v7 long-form styles no longer get a spurious fa class', function(assert) { + // The bug in #776: these hit the v4 shorthand branch and came out as + // "fa fa-solid fa-user". That stray `fa` fights fa-regular for font-weight + // and fa-brands for font-family. + assertChildIcon(assert, 'fa-solid fa-user', ['fa-solid', 'fa-user'], 'v6 solid'); + assertChildIcon(assert, 'fa-regular fa-user', ['fa-regular', 'fa-user'], 'v6 regular'); + assertChildIcon(assert, 'fa-light fa-user', ['fa-light', 'fa-user'], 'v6 light'); + assertChildIcon(assert, 'fa-thin fa-user', ['fa-thin', 'fa-user'], 'v6 thin'); + assertChildIcon(assert, 'fa-brands fa-github', ['fa-brands', 'fa-github'], 'v6 brands'); +}); + +QUnit.test('v6/v7 family plus style combinations are passed through untouched', function(assert) { + assertChildIcon(assert, 'fa-classic fa-solid fa-user', ['fa-classic', 'fa-solid', 'fa-user'], 'classic'); + assertChildIcon(assert, 'fa-sharp fa-solid fa-user', ['fa-sharp', 'fa-solid', 'fa-user'], 'sharp'); + assertChildIcon(assert, 'fa-duotone fa-solid fa-user', ['fa-duotone', 'fa-solid', 'fa-user'], 'duotone'); + assertChildIcon(assert, 'fa-sharp-duotone fa-solid fa-user', ['fa-sharp-duotone', 'fa-solid', 'fa-user'], 'sharp-duotone'); +}); + +QUnit.test('v7 per-pack short prefixes are passed through untouched', function(assert) { + // Matched by shape rather than from a list, so a pack added in a later + // release needs no change here. + assertChildIcon(assert, 'fasds fa-user', ['fasds', 'fa-user'], 'sharp-duotone solid'); + assertChildIcon(assert, 'fasr fa-user', ['fasr', 'fa-user'], 'sharp regular'); + assertChildIcon(assert, 'fat fa-user', ['fat', 'fa-user'], 'thin'); + assertChildIcon(assert, 'fands fa-user', ['fands', 'fa-user'], 'notdog-duo solid'); +}); + +// ------------------------------------------------- built-in icons, unchanged + +QUnit.test('built-in icon names still style the item itself', function(assert) { + var rendered = renderIconFa('edit'); + assert.notOk(rendered.hasChildIcon, 'no child is created'); + assert.ok(rendered.itemClasses.indexOf('context-menu-icon') !== -1, 'item keeps context-menu-icon'); + assert.ok(rendered.itemClasses.indexOf('context-menu-icon-edit') !== -1, 'item gets context-menu-icon-edit'); + assert.notOk(rendered.itemClasses.indexOf('context-menu-icon--fa5') !== -1, 'item is not treated as Font Awesome'); +}); + +QUnit.test('a short built-in icon name beginning with fa is not mistaken for Font Awesome', function(assert) { + // Detection keys off an `fa-` token precisely so a name like this, which a + // caller may well have defined in their own CSS, keeps its old meaning. + var rendered = renderIconFa('fav'); + assert.notOk(rendered.hasChildIcon, 'no child is created'); + assert.ok(rendered.itemClasses.indexOf('context-menu-icon-fav') !== -1, 'item gets context-menu-icon-fav'); + assert.notOk(rendered.itemClasses.indexOf('context-menu-icon--fa5') !== -1, 'item is not treated as Font Awesome'); +}); + +QUnit.test('a function icon is untouched by any of this', function(assert) { + var rendered = renderIconFa(function() { + return 'from-a-callback'; + }); + assert.notOk(rendered.hasChildIcon, 'no child is created'); + assert.ok(rendered.itemClasses.indexOf('from-a-callback') !== -1, 'the returned class is applied to the item'); +}); From dfad91401592c3d42a1069c157d7adf806b07d44 Mon Sep 17 00:00:00 2001 From: bjorn Date: Thu, 30 Jul 2026 10:18:09 +0200 Subject: [PATCH 2/2] refactor: drop the regex from Font Awesome detection Review feedback: the previous commit claimed not to enumerate versions, then buried an enumeration inside a fuzzy pattern: /^(?:fa[a-z]{0,4}|fa-(?:classic|sharp|sharp-duotone|duotone|solid|regular|light|thin|brands))$/ Two simplifications make it unnecessary. A short prefix never contains a hyphen. `fa`, `fas`, `fasds`, `fands` are all hyphen-free, while icon names and modifiers (`fa-user`, `fa-lg`) always have one. So telling a prefix from an icon name needs no list, and the `fa[a-z]{0,4}` guesswork goes away with it - along with its false positives on words like "fancy". Only the styles need listing, not the families. Version 6 and 7 always name a style next to the family, so `fa-notdog-duo fa-solid fa-user` is recognised through its `fa-solid`. A family added after this code was written needs no change, which is what the pattern was reaching for in the first place. Added a test for exactly that case. What is left is a padded string of nine class names and two `indexOf` checks. The only remaining regex is the `split(/\s+/)` that tokenises the class list. The padded-string form also avoids an icon named "constructor" matching Object.prototype, which an object lookup would have. No behaviour change: 143 of 148 tests are unchanged against master, the same set as before, and the 5 that differ are the previously broken syntaxes plus the new unlisted-family case. --- src/jquery.contextMenu.js | 52 ++++++++++++++----------- test/unit/font-awesome-versions.test.js | 14 ++++++- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/jquery.contextMenu.js b/src/jquery.contextMenu.js index bd6c65ef..8b257d91 100644 --- a/src/jquery.contextMenu.js +++ b/src/jquery.contextMenu.js @@ -2595,25 +2595,23 @@ // // // - // and v7 keeps adding packs, each with its own short prefix (`fasds` for - // sharp-duotone, `fands` for notdog-duo, ...), so the set is not fixed and - // is not enumerated here. Instead of matching a version, the two helpers - // below answer the only two questions that actually matter: + // and v7 keeps adding packs. Rather than tracking a version, the two + // helpers below answer the only two questions this plugin actually has: // - // 1. is this an icon-library class list at all, or one of this plugin's - // own built-in icon names? -> a `fa-*` token says Font Awesome. - // 2. does that class list already say which family/style to use? -> if - // not, and only then, v4's base `fa` class is supplied, which is what - // makes the `icon: "fa-user"` shorthand work. - // - // Detection deliberately keys off a `fa-*` token rather than a bare `fa…` - // prefix, because a bare prefix would also swallow short built-in icon - // names such as `icon: "fav"`. - - // a family or style class: the v6/v7 long forms, plus any short prefix - // (`fa`, `fas`, `fab`, `fass`, `fasds`, ...). The short prefixes are matched - // by shape so a pack added in a future release keeps working untouched. - var faFamilyOrStyle = /^(?:fa[a-z]{0,4}|fa-(?:classic|sharp|sharp-duotone|duotone|solid|regular|light|thin|brands))$/; + // 1. is this a Font Awesome class list, or one of this plugin's own + // built-in icon names? + // 2. does that class list already say which family and style to use, or + // does v4's base `fa` still need supplying? Supplying it is what makes + // the `icon: "fa-user"` shorthand work. + + // Font Awesome's style classes, plus the families that existed before v7's + // packs. This needs no entry per pack: v6 and v7 always name a style next + // to the family, so `fa-notdog-duo fa-solid fa-user` is recognised by its + // `fa-solid`. Kept as a padded string and matched with the ' x ' idiom used + // elsewhere in this file, so an icon literally called "constructor" cannot + // collide with Object.prototype. + var faFamilyAndStyleClasses = ' fa-solid fa-regular fa-light fa-thin fa-brands' + + ' fa-duotone fa-classic fa-sharp fa-sharp-duotone '; function faClassTokens(icon) { if (typeof icon !== 'string') { @@ -2622,7 +2620,9 @@ return trimText(icon).split(/\s+/); } - // does this icon value name a Font Awesome icon? + // An `fa-` token says Font Awesome. Keying off that rather than a bare `fa` + // prefix is what keeps a short built-in icon name such as `icon: "fav"` out + // of these paths. function isFontAwesomeIcon(icon) { var tokens = faClassTokens(icon), i; for (i = 0; i < tokens.length; i++) { @@ -2633,11 +2633,19 @@ return false; } - // does it already carry the family/style, or does v4's `fa` need adding? function namesFontAwesomeFamily(icon) { - var tokens = faClassTokens(icon), i; + var tokens = faClassTokens(icon), token, i; for (i = 0; i < tokens.length; i++) { - if (faFamilyOrStyle.test(tokens[i])) { + token = tokens[i]; + // A short prefix carries family and style together and never + // contains a hyphen: `fa` in v4, `fas`/`far`/`fab` in v5, and the + // per-pack ones v7 keeps adding such as `fasds` and `fands`. Icon + // names and modifiers such as `fa-lg` always do contain one, so + // telling the two apart needs no list of prefixes at all. + if (token.indexOf('fa') === 0 && token.indexOf('-') === -1) { + return true; + } + if (faFamilyAndStyleClasses.indexOf(' ' + token + ' ') !== -1) { return true; } } diff --git a/test/unit/font-awesome-versions.test.js b/test/unit/font-awesome-versions.test.js index 3f9be7c5..dc9d8099 100644 --- a/test/unit/font-awesome-versions.test.js +++ b/test/unit/font-awesome-versions.test.js @@ -125,14 +125,24 @@ QUnit.test('v6/v7 family plus style combinations are passed through untouched', }); QUnit.test('v7 per-pack short prefixes are passed through untouched', function(assert) { - // Matched by shape rather than from a list, so a pack added in a later - // release needs no change here. + // Recognised by having no hyphen rather than from a list of prefixes, so a + // pack added in a later release needs no change in the plugin. assertChildIcon(assert, 'fasds fa-user', ['fasds', 'fa-user'], 'sharp-duotone solid'); assertChildIcon(assert, 'fasr fa-user', ['fasr', 'fa-user'], 'sharp regular'); assertChildIcon(assert, 'fat fa-user', ['fat', 'fa-user'], 'thin'); assertChildIcon(assert, 'fands fa-user', ['fands', 'fa-user'], 'notdog-duo solid'); }); +QUnit.test('a long-form family the plugin has never heard of still works', function(assert) { + // The point of listing only styles: a v7 pack, or one released after this + // code was written, is recognised through the style class that always sits + // beside it. No plugin change needed when Font Awesome adds a family. + assertChildIcon(assert, 'fa-notdog-duo fa-solid fa-user', + ['fa-notdog-duo', 'fa-solid', 'fa-user'], 'unlisted family + known style'); + assertChildIcon(assert, 'fa-whiteboard fa-regular fa-user', + ['fa-whiteboard', 'fa-regular', 'fa-user'], 'another unlisted family'); +}); + // ------------------------------------------------- built-in icons, unchanged QUnit.test('built-in icon names still style the item itself', function(assert) {