Skip to content

Commit

Permalink
refactor: move type matching to own function
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Kesselberg <[email protected]>
  • Loading branch information
kesselb committed Oct 11, 2023
1 parent 106bd3b commit 1910947
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 23 deletions.
28 changes: 5 additions & 23 deletions src/components/ContactDetails/ContactDetailsProperty.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import PropertyText from '../Properties/PropertyText.vue'
import PropertyMultipleText from '../Properties/PropertyMultipleText.vue'
import PropertyDateTime from '../Properties/PropertyDateTime.vue'
import PropertySelect from '../Properties/PropertySelect.vue'
import { matchTypes } from '../../utils/matchTypes.ts'

export default {
name: 'ContactDetailsProperty',
Expand Down Expand Up @@ -246,29 +247,10 @@ export default {
// we only use uppercase strings
.map(str => str.toUpperCase())

// Compare array and score them by how many matches they have to the selected type
// sorting directly is cleaner but slower
// https://jsperf.com/array-map-and-intersection-perf
const matchingTypes = this.propModel.options
.map(type => {
let score = 0
const types = type.id.split(',') // "WORK,HOME" => ['WORK', 'HOME']

if (types.length === selectedType.length) {
// additional point for same length
score++
}

const intersection = types.filter(value => selectedType.includes(value))
score = score + intersection.length

return { type, score }
})

// Sort by score, filtering out the null score and selecting the first match
const matchingType = matchingTypes
.sort((a, b) => b.score - a.score)
.filter(type => type.score > 0)[0]
const matchingType = matchTypes(
selectedType,
this.propModel.options,
)

if (matchingType) {
return matchingType.type
Expand Down
36 changes: 36 additions & 0 deletions src/utils/matchTypes.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <[email protected]>
*
* @author Daniel Kesselberg <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Match a list of types against the available types
*
* @param {Array<string>} selectedTypes
* @param {Array<{id: string, name: string}>} options
*/
export declare function matchTypes(selectedTypes: Array<string>, options: Array<{
id: string;
name: string;
}>): {
type: {
id: string;
name: string;
};
score: number;
} | undefined;
50 changes: 50 additions & 0 deletions src/utils/matchTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <[email protected]>
*
* @author Daniel Kesselberg <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* Match a list of types against the available types
*
* @param {Array<string>} selectedTypes
* @param {Array<{id: string, name: string}>} options
*/
export function matchTypes(selectedTypes: Array<string>, options: Array<{id: string, name: string}>) {
const items = options.map(option => {
let score = 0
const types = option.id.split(',') // "WORK,HOME" => ['WORK', 'HOME']

const intersection = types.filter(value => selectedTypes.includes(value))
score = score + intersection.length

if (selectedTypes.length === types.length && selectedTypes.length === intersection.length) {
score++
}

return {
type: option,
score,
}
})

return items
.filter(value => value.score > 0)
.sort((a, b) => b.score - a.score)
.shift()
}
133 changes: 133 additions & 0 deletions tests/javascript/utils/matchTypes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* @copyright Copyright (c) 2023 Daniel Kesselberg <[email protected]>
*
* @author Daniel Kesselberg <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { matchTypes } from '../../../src/utils/matchTypes'
import rfcProps from '../../../src/models/rfcProps.js'

describe('utils/matchTypes test suite', () => {

describe('impp', () => {
it('matches', () => {
const selectedTypes = ['XMPP']

const match = matchTypes(
selectedTypes,
rfcProps.properties.impp.options,
)

expect(match).toMatchObject({
type: { id: 'XMPP', name: 'XMPP' },
score: 2,
})
})

it('does not match', () => {
const selectedTypes = ['TEST']

const match = matchTypes(
selectedTypes,
rfcProps.properties.impp.options,
)

expect(match).toBeUndefined()
})
})

describe('tel', () => {
it('complete match, one type', () => {
const selectedTypes = ['VOICE']

const match = matchTypes(
selectedTypes,
rfcProps.properties.tel.options,
)

expect(match).toMatchObject({
type: { id: 'VOICE', name: 'Voice' },
score: 2,
})
})

it('complete match, two types', () => {
const selectedTypes = ['HOME', 'VOICE']

const match = matchTypes(
selectedTypes,
rfcProps.properties.tel.options,
)

expect(match).toMatchObject({
type: { id: 'HOME,VOICE', name: 'Home' },
score: 3,
})
})

it('partial match, two types', () => {
const selectedTypes = ['HOME', 'VOICE']

const options = [
{ id: 'HOME,VOICE,TEST', name: 'Home' },
{ id: 'HOME,VOICE', name: 'Home' },
{ id: 'HOME', name: 'Home' },
{ id: 'WORK,VOICE,TEST', name: 'Work' },
{ id: 'WORK,VOICE', name: 'Work' },
{ id: 'WORK', name: 'Work' },
{ id: 'VOICE', name: 'Voice' },
{ id: 'TEST', name: 'Test' },
]

const match = matchTypes(
selectedTypes,
options,
)

expect(match).toMatchObject({
type: { id: 'HOME,VOICE', name: 'Home' },
score: 3,
})
})

it('does not match', () => {
const selectedType = ['TEST']

const match = matchTypes(
selectedType,
rfcProps.properties.tel.options,
)

expect(match).toBeUndefined()
})
})

describe('misc', () => {
it('empty list', () => {
const selectedType = ['TEST']

const match = matchTypes(
selectedType,
[],
)

expect(match).toBeUndefined()
})
})
})

0 comments on commit 1910947

Please sign in to comment.