From 49e57f8c6175c8fa30651c16daff2b644a92182b Mon Sep 17 00:00:00 2001 From: William Newman <3382274+newmanw@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:39:40 -0600 Subject: [PATCH] fix(web-app): show default map icon for forms without select fields The Default symbology card was only rendered when a form had select fields and a primary field was chosen, making the default icon unreachable otherwise. It now always renders and displays the actual uploaded icon via getIconUrl instead of a static placeholder. --- .../form-details/form-details.component.html | 64 ++++++------ .../form-details/form-details.component.scss | 5 +- .../form-details.component.spec.ts | 98 +++++++++++++++++++ 3 files changed, 131 insertions(+), 36 deletions(-) diff --git a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html index 8fc4dfcc3..b7dad3988 100644 --- a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html +++ b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html @@ -162,51 +162,45 @@
-
- place -
- Default - No symbology configured + +
+
+ Default + No primary or secondary value +
+
-
+
+
+
+ icon +
+
+ place +
+
+
+
+
+
+
+
info

No Select fields available

-

Add a Select field to the form to configure map symbology options.

+

Add a Select field to the form to configure additional map symbology options.

- -
-
- Default - No primary or secondary value -
- -
-
-
-
- place -
-
-
-
-
-
-
-
- Primary Field diff --git a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss index a3b086f48..e83b235c1 100644 --- a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss +++ b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss @@ -414,6 +414,7 @@ .default-symbology-card { margin-bottom: 16px; + padding-bottom: 8px; display: inline-flex; flex-direction: column; min-width: 220px; @@ -441,11 +442,13 @@ .symbology-variant-item { min-width: 220px; flex-shrink: 0; + padding-bottom: 8px; } .variant-card-header { display: flex; align-items: center; + gap: 8px; padding: 4px 4px 0 12px; } @@ -472,7 +475,7 @@ } .icon-preview { - height: 48px; + height: 36px; display: flex; align-items: center; justify-content: center; diff --git a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts index 6deb2f700..94a9a785d 100644 --- a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts +++ b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts @@ -451,6 +451,104 @@ describe('FormDetailsComponent', () => { }); }); + describe('getDropdownFields', () => { + it('returns empty array when form has no fields', () => { + component.form = { name: 'Test Form', color: '#ff0000', fields: [] }; + + expect(component.getDropdownFields()).toEqual([]); + }); + + it('excludes non-dropdown fields', () => { + component.form = { + name: 'Test Form', + color: '#ff0000', + fields: [ + { name: 'notes', type: 'textfield' } as any, + { name: 'count', type: 'numberfield' } as any + ] + }; + + expect(component.getDropdownFields()).toEqual([]); + }); + + it('includes dropdown and userDropdown fields, excluding multiselect and archived', () => { + const dropdown = { name: 'status', type: 'dropdown' } as any; + const userDropdown = { name: 'assignee', type: 'userDropdown' } as any; + const multiselect = { + name: 'tags', + type: 'dropdown', + multiselect: true + } as any; + const archived = { + name: 'old', + type: 'dropdown', + archived: true + } as any; + + component.form = { + name: 'Test Form', + color: '#ff0000', + fields: [dropdown, userDropdown, multiselect, archived] + }; + + expect(component.getDropdownFields()).toEqual([dropdown, userDropdown]); + }); + + it('excludes the field named by excludeField', () => { + const primary = { name: 'status', type: 'dropdown' } as any; + const secondary = { name: 'severity', type: 'dropdown' } as any; + + component.form = { + name: 'Test Form', + color: '#ff0000', + fields: [primary, secondary] + }; + + expect(component.getDropdownFields('status')).toEqual([secondary]); + }); + }); + + describe('getIconUrl', () => { + it('returns null when no icons have been cached', () => { + expect(component.getIconUrl('')).toBeNull(); + }); + + it('returns the form-wide default icon for an empty primary', () => { + (component as any).iconCache = { icon: 'default-icon-url' }; + + expect(component.getIconUrl('')).toBe('default-icon-url'); + }); + + it('returns the icon for a primary choice', () => { + (component as any).iconCache = { + Active: { icon: 'active-icon-url' }, + icon: 'default-icon-url' + }; + + expect(component.getIconUrl('Active')).toBe('active-icon-url'); + }); + + it('returns the icon for a primary/variant combination', () => { + (component as any).iconCache = { + Active: { High: 'active-high-icon-url', icon: 'active-icon-url' } + }; + + expect(component.getIconUrl('Active', 'High')).toBe( + 'active-high-icon-url' + ); + }); + + it('falls back to the primary icon when the variant has no icon', () => { + (component as any).iconCache = { + Active: { icon: 'active-icon-url' } + }; + + expect(component.getIconUrl('Active', 'High')).toBe( + 'active-icon-url' + ); + }); + }); + describe('exportForm', () => { let mockAnchor: any;