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`] = `
error message
- +
`; @@ -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`] = `
error message
- +
`; @@ -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 <> extends ReadonlyMap +// ? K +// : never; diff --git a/src/components/index.ts b/src/components/index.ts index 14e16a2..d54b1d3 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -10,3 +10,4 @@ export { TileIcon } from './TileIcon'; export { Dropdown } from './Dropdown'; export { Breadcrumbs } from './Breadcrumbs'; export { Spinner } from './Spinner'; +export { RecordTypeIcon } from './RecordTypeIcon';