From 7ac0e6ec8a6cbdbcb96bccf00d3cc9a0480468f9 Mon Sep 17 00:00:00 2001
From: Joshua Graber <68428039+joshuagraber@users.noreply.github.com>
Date: Thu, 12 Sep 2024 11:01:09 -0400
Subject: [PATCH] feat: add record type icon to form input labels (#103)
---
package-lock.json | 109 ++++++++++++++++++
package.json | 5 +
.../Form/__snapshots__/form.spec.ts.snap | 72 ++++++++++--
src/components/Input/PdapInput.vue | 14 ++-
.../Input/__snapshots__/input.spec.ts.snap | 36 +++++-
.../quick-search-form.spec.ts.snap | 12 +-
.../RecordTypeIcon/RecordTypeIcon.vue | 104 +++++++++++++++++
.../__snapshots__/recordTypeIcon.spec.ts.snap | 45 ++++++++
src/components/RecordTypeIcon/index.ts | 1 +
.../RecordTypeIcon/recordTypeIcon.spec.ts | 96 +++++++++++++++
src/components/RecordTypeIcon/util.ts | 14 +++
src/components/index.ts | 1 +
12 files changed, 488 insertions(+), 21 deletions(-)
create mode 100644 src/components/RecordTypeIcon/RecordTypeIcon.vue
create mode 100644 src/components/RecordTypeIcon/__snapshots__/recordTypeIcon.spec.ts.snap
create mode 100644 src/components/RecordTypeIcon/index.ts
create mode 100644 src/components/RecordTypeIcon/recordTypeIcon.spec.ts
create mode 100644 src/components/RecordTypeIcon/util.ts
diff --git a/package-lock.json b/package-lock.json
index bbacb0f..c891ce5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,11 @@
"eslint-config"
],
"dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.6.0",
+ "@fortawesome/free-regular-svg-icons": "^6.6.0",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
+ "@fortawesome/vue-fontawesome": "^3.0.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"fs-extra": "^11.1.1",
@@ -1339,6 +1344,67 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@fortawesome/fontawesome-common-types": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
+ "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/fontawesome-svg-core": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
+ "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-brands-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-regular-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/free-solid-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
+ "dependencies": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@fortawesome/vue-fontawesome": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz",
+ "integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==",
+ "peerDependencies": {
+ "@fortawesome/fontawesome-svg-core": "~1 || ~6",
+ "vue": ">= 3.0.0 < 4"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
@@ -18038,6 +18104,49 @@
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w=="
},
+ "@fortawesome/fontawesome-common-types": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
+ "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="
+ },
+ "@fortawesome/fontawesome-svg-core": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
+ "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ }
+ },
+ "@fortawesome/free-brands-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ }
+ },
+ "@fortawesome/free-regular-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ }
+ },
+ "@fortawesome/free-solid-svg-icons": {
+ "version": "6.6.0",
+ "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
+ "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "6.6.0"
+ }
+ },
+ "@fortawesome/vue-fontawesome": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz",
+ "integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==",
+ "requires": {}
+ },
"@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
diff --git a/package.json b/package.json
index acac0a1..25b427a 100644
--- a/package.json
+++ b/package.json
@@ -85,6 +85,11 @@
"vue-tsc": "^1.8.22"
},
"dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-brands-svg-icons": "^6.6.0",
+ "@fortawesome/free-regular-svg-icons": "^6.6.0",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
+ "@fortawesome/vue-fontawesome": "^3.0.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"fs-extra": "^11.1.1",
diff --git a/src/components/Form/__snapshots__/form.spec.ts.snap b/src/components/Form/__snapshots__/form.spec.ts.snap
index d3f1646..5f4d03f 100644
--- a/src/components/Form/__snapshots__/form.spec.ts.snap
+++ b/src/components/Form/__snapshots__/form.spec.ts.snap
@@ -6,32 +6,56 @@ exports[`Form component > Renders component in form error state 1`] = `
-
+
-
+
-
+
-
+
-
+
-
+
@@ -43,32 +67,56 @@ exports[`Form component > Renders component in static state 1`] = `
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/src/components/Input/PdapInput.vue b/src/components/Input/PdapInput.vue
index fe71a60..fdcf99e 100644
--- a/src/components/Input/PdapInput.vue
+++ b/src/components/Input/PdapInput.vue
@@ -23,7 +23,12 @@
{{ error }}
-
+
@@ -37,6 +42,7 @@ import {
} from './types';
import PdapInputText from './Text/InputText.vue';
import PdapInputCheckbox from './Checkbox/InputCheckbox.vue';
+import RecordTypeIcon from '../RecordTypeIcon/RecordTypeIcon.vue';
const props = withDefaults(defineProps(), {});
@@ -117,4 +123,10 @@ const errorMessageId = computed(() => `pdap-${props.name}-input-error`);
@apply cursor-pointer;
}
}
+
+/* stylelint-disable */
+.svg-inline--fa {
+ @apply ml-2;
+}
+/* stylelint-enable */
diff --git a/src/components/Input/__snapshots__/input.spec.ts.snap b/src/components/Input/__snapshots__/input.spec.ts.snap
index f0913c8..52e20c3 100644
--- a/src/components/Input/__snapshots__/input.spec.ts.snap
+++ b/src/components/Input/__snapshots__/input.spec.ts.snap
@@ -4,7 +4,11 @@ exports[`Input component > Renders checkbox input in error state 1`] = `
`;
@@ -12,7 +16,11 @@ exports[`Input component > Renders checkbox input in okay state 1`] = `
-
+
`;
@@ -20,7 +28,11 @@ exports[`Input component > Renders password input in error state 1`] = `
`;
@@ -28,7 +40,11 @@ exports[`Input component > Renders password input in okay state 1`] = `
-
+
`;
@@ -36,7 +52,11 @@ exports[`Input component > Renders text input in error state 1`] = `
error message
-
+
`;
@@ -44,6 +64,10 @@ exports[`Input component > Renders text input in okay state 1`] = `
-
+
`;
diff --git a/src/components/QuickSearchForm/__snapshots__/quick-search-form.spec.ts.snap b/src/components/QuickSearchForm/__snapshots__/quick-search-form.spec.ts.snap
index 7a250fd..ee3186a 100644
--- a/src/components/QuickSearchForm/__snapshots__/quick-search-form.spec.ts.snap
+++ b/src/components/QuickSearchForm/__snapshots__/quick-search-form.spec.ts.snap
@@ -12,12 +12,20 @@ exports[`QuickSearchForm component > Renders a QuickSearchForm 1`] = `
-
+
-
+
diff --git a/src/components/RecordTypeIcon/RecordTypeIcon.vue b/src/components/RecordTypeIcon/RecordTypeIcon.vue
new file mode 100644
index 0000000..41d36d5
--- /dev/null
+++ b/src/components/RecordTypeIcon/RecordTypeIcon.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
diff --git a/src/components/RecordTypeIcon/__snapshots__/recordTypeIcon.spec.ts.snap b/src/components/RecordTypeIcon/__snapshots__/recordTypeIcon.spec.ts.snap
new file mode 100644
index 0000000..10481c5
--- /dev/null
+++ b/src/components/RecordTypeIcon/__snapshots__/recordTypeIcon.spec.ts.snap
@@ -0,0 +1,45 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`RecordTypeIcon > does not render an icon for an invalid record type 1`] = ``;
+
+exports[`RecordTypeIcon > handles record types with ampersand correctly 1`] = `
+
+`;
+
+exports[`RecordTypeIcon > handles top-level record types correctly 1`] = `
+
+`;
+
+exports[`RecordTypeIcon > handles top-level record types correctly 2`] = `
+
+`;
+
+exports[`RecordTypeIcon > handles top-level record types correctly 3`] = `
+
+`;
+
+exports[`RecordTypeIcon > handles top-level record types correctly 4`] = `
+
+`;
+
+exports[`RecordTypeIcon > handles top-level record types correctly 5`] = `
+
+`;
+
+exports[`RecordTypeIcon > renders the correct icon for a valid record type 1`] = `
+
+`;
diff --git a/src/components/RecordTypeIcon/index.ts b/src/components/RecordTypeIcon/index.ts
new file mode 100644
index 0000000..1cea958
--- /dev/null
+++ b/src/components/RecordTypeIcon/index.ts
@@ -0,0 +1 @@
+export { default as RecordTypeIcon } from './RecordTypeIcon.vue';
diff --git a/src/components/RecordTypeIcon/recordTypeIcon.spec.ts b/src/components/RecordTypeIcon/recordTypeIcon.spec.ts
new file mode 100644
index 0000000..c2f13d1
--- /dev/null
+++ b/src/components/RecordTypeIcon/recordTypeIcon.spec.ts
@@ -0,0 +1,96 @@
+import { shallowMount } from '@vue/test-utils';
+import RecordTypeIcon from './RecordTypeIcon.vue';
+import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
+import {
+ faPersonMilitaryToPerson,
+ faPersonMilitaryPointing,
+ faBuildingShield,
+ faFileShield,
+ faBuildingColumns,
+} from '@fortawesome/free-solid-svg-icons';
+import { describe, expect, it } from 'vitest';
+
+describe('RecordTypeIcon', () => {
+ it('renders the correct icon for a valid record type', () => {
+ const recordType = 'Incident Reports';
+ const wrapper = shallowMount(RecordTypeIcon, {
+ props: { recordType },
+ global: {
+ stubs: {
+ FontAwesomeIcon,
+ },
+ },
+ });
+
+ expect(wrapper.findComponent(FontAwesomeIcon).props('icon')).toEqual(
+ faPersonMilitaryToPerson
+ );
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+
+ it('does not render an icon for an invalid record type', () => {
+ const recordType = 'Invalid Record Type';
+ const wrapper = shallowMount(RecordTypeIcon, {
+ props: { recordType },
+ global: {
+ stubs: {
+ FontAwesomeIcon,
+ },
+ },
+ });
+
+ expect(wrapper.findComponent(FontAwesomeIcon).exists()).toBe(false);
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+
+ it('handles record types with ampersand correctly', () => {
+ const recordType = 'Police & public interactions';
+ const wrapper = shallowMount(RecordTypeIcon, {
+ props: { recordType },
+ global: {
+ stubs: {
+ FontAwesomeIcon,
+ },
+ },
+ });
+
+ expect(wrapper.findComponent(FontAwesomeIcon).props('icon')).toEqual(
+ faPersonMilitaryToPerson
+ );
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+
+ it('handles top-level record types correctly', () => {
+ const recordTypes = [
+ 'Police & public interactions',
+ 'Info about officers',
+ 'Info about agencies',
+ 'Agency-published resources',
+ 'Jails & Courts specific',
+ ];
+
+ const expectedIcons = [
+ faPersonMilitaryToPerson,
+ faPersonMilitaryPointing,
+ faBuildingShield,
+ faFileShield,
+ faBuildingColumns,
+ ];
+
+ recordTypes.forEach((recordType, index) => {
+ const wrapper = shallowMount(RecordTypeIcon, {
+ props: { recordType },
+ global: {
+ stubs: {
+ FontAwesomeIcon,
+ },
+ },
+ });
+
+ expect(wrapper.findComponent(FontAwesomeIcon).props('icon')).toEqual(
+ expectedIcons[index]
+ );
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+});
diff --git a/src/components/RecordTypeIcon/util.ts b/src/components/RecordTypeIcon/util.ts
new file mode 100644
index 0000000..4385800
--- /dev/null
+++ b/src/components/RecordTypeIcon/util.ts
@@ -0,0 +1,14 @@
+/**
+ * Creates map with type inference
+ *
+ * UNUSED FOR NOW, but we may need it later
+ */
+// export function ReadonlyMapWithStringKeys(
+// iterable: Iterable<[K, V]>
+// ): ReadonlyMap {
+// return new Map(iterable);
+// }
+
+// type MapKeys = typeof <