Skip to content

Commit c1fa05e

Browse files
authored
fix(dashboard): include missing vuetify mdi icons (#6970)
Export the required icon set and expand it with icons used by Vuetify internals that are not detected by static source scans. Regenerate the MDI subset assets and update tests to assert that all required icons are always included and deduplicated.
1 parent 2b5d86b commit c1fa05e

5 files changed

Lines changed: 97 additions & 13 deletions

File tree

dashboard/scripts/subset-mdi-font.mjs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,42 @@ const UTILITY_CLASSES = new Set([
3434
]);
3535

3636
// Icons used indirectly by Vuetify internals, so they won't appear in src/ static scans.
37-
const REQUIRED_ICONS = new Set([
37+
export const REQUIRED_ICONS = new Set([
3838
"mdi-radiobox-blank",
3939
"mdi-radiobox-marked",
40+
"mdi-menu-down",
41+
"mdi-menu-right",
42+
"mdi-check-circle",
43+
"mdi-information",
44+
"mdi-alert-circle",
45+
"mdi-close-circle",
46+
"mdi-chevron-down",
47+
"mdi-chevron-up",
48+
"mdi-chevron-left",
49+
"mdi-chevron-right",
50+
"mdi-check",
51+
"mdi-close",
52+
"mdi-checkbox-marked",
53+
"mdi-checkbox-blank-outline",
54+
"mdi-minus-box",
55+
"mdi-circle",
56+
"mdi-arrow-up",
57+
"mdi-arrow-down",
58+
"mdi-menu",
59+
"mdi-pencil",
60+
"mdi-star-outline",
61+
"mdi-star",
62+
"mdi-star-half-full",
63+
"mdi-cached",
64+
"mdi-page-first",
65+
"mdi-page-last",
66+
"mdi-unfold-more-horizontal",
67+
"mdi-paperclip",
68+
"mdi-plus",
69+
"mdi-minus",
70+
"mdi-calendar",
71+
"mdi-eyedropper",
72+
"mdi-cloud-upload",
4073
]);
4174

4275
// Regex to match individual icon class definitions in MDI CSS

dashboard/src/assets/mdi-subset/materialdesignicons-subset.css

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Auto-generated MDI subset – 237 icons */
1+
/* Auto-generated MDI subset – 248 icons */
22
/* Do not edit manually. Run: pnpm run subset-icons */
33

44
@font-face {
@@ -120,6 +120,10 @@
120120
content: "\F00E4";
121121
}
122122

123+
.mdi-cached::before {
124+
content: "\F00E8";
125+
}
126+
123127
.mdi-calendar::before {
124128
content: "\F00ED";
125129
}
@@ -364,6 +368,10 @@
364368
content: "\F06D0";
365369
}
366370

371+
.mdi-eyedropper::before {
372+
content: "\F020A";
373+
}
374+
367375
.mdi-file::before {
368376
content: "\F0214";
369377
}
@@ -620,6 +628,14 @@
620628
content: "\F035C";
621629
}
622630

631+
.mdi-menu-down::before {
632+
content: "\F035D";
633+
}
634+
635+
.mdi-menu-right::before {
636+
content: "\F035F";
637+
}
638+
623639
.mdi-message-off-outline::before {
624640
content: "\F164E";
625641
}
@@ -644,6 +660,10 @@
644660
content: "\F0374";
645661
}
646662

663+
.mdi-minus-box::before {
664+
content: "\F0375";
665+
}
666+
647667
.mdi-note-text-outline::before {
648668
content: "\F11D7";
649669
}
@@ -676,6 +696,18 @@
676696
content: "\F03D6";
677697
}
678698

699+
.mdi-page-first::before {
700+
content: "\F0600";
701+
}
702+
703+
.mdi-page-last::before {
704+
content: "\F0601";
705+
}
706+
707+
.mdi-paperclip::before {
708+
content: "\F03E2";
709+
}
710+
679711
.mdi-pause::before {
680712
content: "\F03E4";
681713
}
@@ -848,6 +880,14 @@
848880
content: "\F1C55";
849881
}
850882

883+
.mdi-star-half-full::before {
884+
content: "\F04D0";
885+
}
886+
887+
.mdi-star-outline::before {
888+
content: "\F04D2";
889+
}
890+
851891
.mdi-stop::before {
852892
content: "\F04DB";
853893
}
@@ -904,6 +944,10 @@
904944
content: "\F0A7A";
905945
}
906946

947+
.mdi-unfold-more-horizontal::before {
948+
content: "\F054F";
949+
}
950+
907951
.mdi-update::before {
908952
content: "\F06B0";
909953
}
Binary file not shown.
Binary file not shown.

dashboard/tests/subsetMdiFont.test.mjs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
resolveUsedIcons,
1212
extractUtilityCss,
1313
ICON_CLASS_PATTERN,
14+
REQUIRED_ICONS,
1415
} from '../scripts/subset-mdi-font.mjs';
1516

1617
// ── Helper: create a temporary directory tree for file-system tests ─────────
@@ -83,9 +84,11 @@ test('scanUsedIcons extracts mdi-* icon names from files', () => {
8384
assert.ok(icons instanceof Set);
8485
assert.ok(icons.has('mdi-home'));
8586
assert.ok(icons.has('mdi-close'));
86-
assert.ok(icons.has('mdi-radiobox-blank'));
87-
assert.ok(icons.has('mdi-radiobox-marked'));
88-
assert.equal(icons.size, 4); // source icons + required radio icons
87+
for (const requiredIcon of REQUIRED_ICONS) {
88+
assert.ok(icons.has(requiredIcon));
89+
}
90+
const expectedIcons = new Set([...REQUIRED_ICONS, 'mdi-home', 'mdi-close']);
91+
assert.deepEqual(icons, expectedIcons);
8992

9093
rmSync(tmp, { recursive: true });
9194
});
@@ -103,25 +106,29 @@ test('scanUsedIcons excludes utility classes', () => {
103106
rmSync(tmp, { recursive: true });
104107
});
105108

106-
test('scanUsedIcons includes required radio icons even when no mdi-* icons are found in source', () => {
109+
test('scanUsedIcons includes all required icons even when no mdi-* icons are found in source', () => {
107110
const tmp = makeTmpDir();
108111
writeFileSync(join(tmp, 'A.vue'), '<div>Hello</div>');
109112

110113
const icons = scanUsedIcons(collectFiles(tmp, ['.vue']));
111-
assert.ok(icons.has('mdi-radiobox-blank'));
112-
assert.ok(icons.has('mdi-radiobox-marked'));
113-
assert.equal(icons.size, 2);
114+
for (const requiredIcon of REQUIRED_ICONS) {
115+
assert.ok(icons.has(requiredIcon));
116+
}
117+
assert.equal(icons.size, REQUIRED_ICONS.size);
114118

115119
rmSync(tmp, { recursive: true });
116120
});
117121

118-
test('scanUsedIcons deduplicates required radio icons when source already references them', () => {
122+
test('scanUsedIcons deduplicates required icons when source already references them', () => {
119123
const tmp = makeTmpDir();
120-
writeFileSync(join(tmp, 'A.vue'), '<v-icon>mdi-radiobox-marked</v-icon><v-icon>mdi-home</v-icon>');
124+
const requiredIcon = [...REQUIRED_ICONS][0];
125+
writeFileSync(join(tmp, 'A.vue'), `<v-icon>${requiredIcon}</v-icon><v-icon>mdi-home</v-icon>`);
121126

122127
const icons = [...scanUsedIcons(collectFiles(tmp, ['.vue']))];
123-
assert.equal(icons.filter(icon => icon === 'mdi-radiobox-marked').length, 1);
124-
assert.ok(icons.includes('mdi-radiobox-blank'));
128+
assert.equal(icons.filter(icon => icon === requiredIcon).length, 1);
129+
for (const builtInRequiredIcon of REQUIRED_ICONS) {
130+
assert.ok(icons.includes(builtInRequiredIcon));
131+
}
125132
assert.ok(icons.includes('mdi-home'));
126133

127134
rmSync(tmp, { recursive: true });

0 commit comments

Comments
 (0)