From 5d6713f245c8ea9fc554a356d3064110dc421151 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Tue, 31 Aug 2021 16:51:18 -0600 Subject: [PATCH] Sort contacts alphabetically Contacts are grouped together by letter, and the groups are listed alphabetically, but the contacts in each group are not sorted alphabetically themselves. Fixes #10318. --- test/jest/index.js | 2 + test/jest/matchers.js | 38 ++++++++++++ test/jest/setup.js | 1 + .../contact-list/contact-list.component.js | 55 +++++++++-------- .../app/contact-list/contact-list.test.js | 60 +++++++++++++++++++ 5 files changed, 130 insertions(+), 26 deletions(-) create mode 100644 test/jest/matchers.js create mode 100644 ui/components/app/contact-list/contact-list.test.js diff --git a/test/jest/index.js b/test/jest/index.js index 098877489808..b68672d34541 100644 --- a/test/jest/index.js +++ b/test/jest/index.js @@ -1,3 +1,5 @@ +import './matchers'; + export { createSwapsMockStore } from './mock-store'; export { renderWithProvider } from './rendering'; export { setBackgroundConnection } from './background'; diff --git a/test/jest/matchers.js b/test/jest/matchers.js new file mode 100644 index 000000000000..01f6955e0e94 --- /dev/null +++ b/test/jest/matchers.js @@ -0,0 +1,38 @@ +import { shallow, mount } from 'enzyme'; + +const SHALLOW_WRAPPER_CONSTRUCTOR = 'ShallowWrapper'; + +function isShallowWrapper(wrapper) { + return wrapper.constructor.name === undefined + ? Boolean(`${wrapper.constructor}`.match(/^function ShallowWrapper\(/u)) + : wrapper.constructor.name === SHALLOW_WRAPPER_CONSTRUCTOR; +} + +function toMatchElement( + actualEnzymeWrapper, + reactInstance, + options = { ignoreProps: true }, +) { + let expectedWrapper; + if (isShallowWrapper(actualEnzymeWrapper)) { + expectedWrapper = shallow(reactInstance); + } else { + expectedWrapper = mount(reactInstance); + } + + const actual = actualEnzymeWrapper.debug({ verbose: true, ...options }); + const expected = expectedWrapper.debug({ verbose: true, ...options }); + const pass = actual === expected; + + return { + pass, + message: 'Expected actual value to match the expected value.', + negatedMessage: 'Did not expect actual value to match the expected value.', + contextualInformation: { + actual: `Actual:\n ${actual}`, + expected: `Expected:\n ${expected}`, + }, + }; +} + +expect.extend({ toMatchElement }); diff --git a/test/jest/setup.js b/test/jest/setup.js index 6176cfc660db..db848f08a8d2 100644 --- a/test/jest/setup.js +++ b/test/jest/setup.js @@ -1,2 +1,3 @@ // This file is for Jest-specific setup only and runs before our Jest tests. import '@testing-library/jest-dom'; +import './matchers'; diff --git a/ui/components/app/contact-list/contact-list.component.js b/ui/components/app/contact-list/contact-list.component.js index 04e3a3abd0a9..713edfcfb98d 100644 --- a/ui/components/app/contact-list/contact-list.component.js +++ b/ui/components/app/contact-list/contact-list.component.js @@ -1,5 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; +import { sortBy } from 'lodash'; import Button from '../../ui/button'; import RecipientGroup from './recipient-group/recipient-group.component'; @@ -50,34 +51,36 @@ export default class ContactList extends PureComponent { } renderAddressBook() { - const contacts = this.props.searchForContacts(); + const unsortedContactsByLetter = this.props + .searchForContacts() + .reduce((obj, contact) => { + const firstLetter = contact.name[0].toUpperCase(); + return { + ...obj, + [firstLetter]: [...(obj[firstLetter] || []), contact], + }; + }, {}); - const contactGroups = contacts.reduce((acc, contact) => { - const firstLetter = contact.name.slice(0, 1).toUpperCase(); - acc[firstLetter] = acc[firstLetter] || []; - const bucket = acc[firstLetter]; - bucket.push(contact); - return acc; - }, {}); + const letters = Object.keys(unsortedContactsByLetter).sort(); - return Object.entries(contactGroups) - .sort(([letter1], [letter2]) => { - if (letter1 > letter2) { - return 1; - } else if (letter1 === letter2) { - return 0; - } - return -1; - }) - .map(([letter, groupItems]) => ( - - )); + const sortedContactGroups = letters.map((letter) => { + return [ + letter, + sortBy(unsortedContactsByLetter[letter], (contact) => { + return contact.name.toLowerCase(); + }), + ]; + }); + + return sortedContactGroups.map(([letter, groupItems]) => ( + + )); } renderMyAccounts() { diff --git a/ui/components/app/contact-list/contact-list.test.js b/ui/components/app/contact-list/contact-list.test.js new file mode 100644 index 000000000000..8646cc70d2d3 --- /dev/null +++ b/ui/components/app/contact-list/contact-list.test.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { shallowWithContext } from '../../../../test/lib/render-helpers'; +import RecipientGroup from './recipient-group'; +import ContactList from '.'; + +describe('Contact List', () => { + describe('given searchForContacts', () => { + it('sorts contacts by name within each letter group', () => { + const contacts = { + Al: { name: 'Al', address: '0x0' }, + aa: { name: 'aa', address: '0x1' }, + Az: { name: 'Az', address: '0x2' }, + Bl: { name: 'Bl', address: '0x3' }, + ba: { name: 'ba', address: '0x4' }, + Bz: { name: 'Bz', address: '0x5' }, + Ccc: { name: 'Ccc', address: '0x6' }, + }; + const searchForContacts = () => { + return Object.values(contacts); + }; + const selectRecipient = () => null; + const selectedAddress = null; + + const wrapper = shallowWithContext( + , + ); + + expect(wrapper).toMatchElement( +
+ + + +
, + { ignoreProps: false }, + ); + }); + }); +});