dev: moved geolocation utils from wccom to here (#38356)
This commit is contained in:
parent
5ba15f84cd
commit
06e6f5012f
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Moved geolocation country matching functions to @woocommerce/onboarding
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"rootDir": "./src",
|
||||
"preset": "../node_modules/@woocommerce/internal-js-tests/jest-preset.js"
|
||||
}
|
|
@ -37,14 +37,19 @@
|
|||
"@wordpress/components": "wp-6.0",
|
||||
"@wordpress/element": "wp-6.0",
|
||||
"@wordpress/i18n": "wp-6.0",
|
||||
"string-similarity": "4.0.4",
|
||||
"gridicons": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@types/string-similarity" : "4.0.0",
|
||||
"@types/wordpress__components": "^19.10.3",
|
||||
"@types/wordpress__data": "^6.0.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@woocommerce/eslint-plugin": "workspace:*",
|
||||
"@woocommerce/internal-style-build": "workspace:*",
|
||||
"@woocommerce/internal-js-tests": "workspace:*",
|
||||
"@wordpress/browserslist-config": "wp-6.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^8.32.0",
|
||||
|
@ -62,6 +67,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"turbo:build": "pnpm run build:js && pnpm run build:css",
|
||||
"turbo:test": "jest --config ./jest.config.json",
|
||||
"prepare": "composer install",
|
||||
"changelog": "composer exec -- changelogger",
|
||||
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
|
||||
|
@ -70,12 +76,15 @@
|
|||
"build:js": "tsc --project tsconfig.json && tsc --project tsconfig-cjs.json",
|
||||
"build:css": "webpack",
|
||||
"start": "concurrently \"tsc --project tsconfig.json --watch\" \"tsc --project tsconfig-cjs.json --watch\" \"webpack --watch\"",
|
||||
"test": "pnpm -w exec turbo run turbo:test --filter=$npm_package_name",
|
||||
"prepack": "pnpm run clean && pnpm run build",
|
||||
"lint:fix": "eslint src --fix"
|
||||
"lint:fix": "eslint src --fix",
|
||||
"test-staged": "jest --bail --config ./jest.config.json --findRelatedTests"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(t|j)s?(x)": [
|
||||
"pnpm lint:fix"
|
||||
"pnpm lint:fix",
|
||||
"pnpm test-staged"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,3 +15,4 @@ export { WooPaymentGatewayConfigure } from './components/WooPaymentGatewayConfig
|
|||
export { WooOnboardingTaskListItem } from './components/WooOnboardingTaskListItem';
|
||||
export { WooOnboardingTaskListHeader } from './components/WooOnboardingTaskListHeader';
|
||||
export { WooOnboardingTask } from './components/WooOnboardingTask';
|
||||
export * from './utils/countries';
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import stringSimilarity from 'string-similarity';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getMappingRegion } from './location-mapping';
|
||||
|
||||
/**
|
||||
* Country state option.
|
||||
*/
|
||||
export type CountryStateOption = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type Location = {
|
||||
country_short?: string;
|
||||
region?: string;
|
||||
city?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a country option for the given location.
|
||||
*/
|
||||
export const findCountryOption = (
|
||||
countryStateOptions: CountryStateOption[],
|
||||
location: Location | undefined,
|
||||
minimumSimilarity = 0.7
|
||||
) => {
|
||||
if ( ! location ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let match = null;
|
||||
let matchSimilarity = minimumSimilarity;
|
||||
// eslint-disable-next-line @wordpress/no-unused-vars-before-return -- don't want to put this inside the loop
|
||||
const mappingRegion = getMappingRegion( location );
|
||||
|
||||
for ( const option of countryStateOptions ) {
|
||||
// Country matches exactly.
|
||||
if ( option.key === location.country_short ) {
|
||||
return option;
|
||||
}
|
||||
|
||||
// Countries have regions such as 'US:CA'.
|
||||
const countryCode = option.key.split( ':' )[ 0 ];
|
||||
if (
|
||||
countryCode === location.country_short &&
|
||||
option.label.includes( '—' )
|
||||
) {
|
||||
const wcRegion = option.label.split( '—' )[ 1 ].trim();
|
||||
|
||||
// Region matches exactly with mapping.
|
||||
if ( mappingRegion === wcRegion ) {
|
||||
return option;
|
||||
}
|
||||
|
||||
// Find the region with the highest similarity.
|
||||
const similarity = Math.max(
|
||||
stringSimilarity.compareTwoStrings(
|
||||
wcRegion,
|
||||
location.region || ''
|
||||
),
|
||||
stringSimilarity.compareTwoStrings(
|
||||
wcRegion,
|
||||
location.city || ''
|
||||
)
|
||||
);
|
||||
if ( similarity >= matchSimilarity ) {
|
||||
match = option;
|
||||
matchSimilarity = similarity;
|
||||
}
|
||||
}
|
||||
}
|
||||
return match;
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Location } from '.';
|
||||
|
||||
/**
|
||||
* This file is used to map a location to a WC region label
|
||||
* so that we can find the correct country option
|
||||
* since WPCOM geolocation returns a different
|
||||
* region or city name.
|
||||
*/
|
||||
|
||||
// Key is the country code, value is an object with keys as region/city names and values as WC region labels.
|
||||
const MAPPING: Record< string, Record< string, string > > = {
|
||||
PH: {
|
||||
'National Capital Region': __( 'Metro Manila', 'woocommerce' ),
|
||||
},
|
||||
IT: {
|
||||
Rome: __( 'Roma', 'woocommerce' ),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a WC mapping region name for the given country, region and city.
|
||||
*/
|
||||
export const getMappingRegion = ( {
|
||||
country_short: countryCode,
|
||||
region = '',
|
||||
city = '',
|
||||
}: Location ) => {
|
||||
if ( ! countryCode ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const countryMapping = MAPPING[ countryCode ];
|
||||
if ( ! countryMapping ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const regionMapping = countryMapping[ region ];
|
||||
if ( regionMapping ) {
|
||||
return regionMapping;
|
||||
}
|
||||
|
||||
const cityMapping = countryMapping[ city ];
|
||||
if ( cityMapping ) {
|
||||
return cityMapping;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MAPPING;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,96 @@
|
|||
/* eslint-disable camelcase */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { findCountryOption } from '../../';
|
||||
import { countryStateOptions } from './country-options';
|
||||
import { locations } from './locations';
|
||||
|
||||
describe( 'findCountryOption', () => {
|
||||
it( 'should return null on undefined location', () => {
|
||||
const location = undefined;
|
||||
expect( findCountryOption( countryStateOptions, location ) ).toEqual(
|
||||
null
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should return null when not found', () => {
|
||||
const location = { country_short: 'US' };
|
||||
expect( findCountryOption( countryStateOptions, location ) ).toBeNull();
|
||||
} );
|
||||
|
||||
it.each( [
|
||||
{
|
||||
location: { country_short: 'TW' },
|
||||
expected: {
|
||||
key: 'TW',
|
||||
label: 'Taiwan',
|
||||
},
|
||||
},
|
||||
{
|
||||
location: { country_short: 'US', region: 'California' },
|
||||
expected: {
|
||||
key: 'US:CA',
|
||||
label: 'United States (US) — California',
|
||||
},
|
||||
},
|
||||
{
|
||||
location: { country_short: 'ES', region: 'Madrid, Comunidad de' },
|
||||
expected: {
|
||||
key: 'ES:M',
|
||||
label: 'Spain — Madrid',
|
||||
},
|
||||
},
|
||||
{
|
||||
location: {
|
||||
country_short: 'AR',
|
||||
region: 'Ciudad Autonoma de Buenos Aires',
|
||||
},
|
||||
expected: {
|
||||
key: 'AR:C',
|
||||
label: 'Argentina — Ciudad Autónoma de Buenos Aires',
|
||||
},
|
||||
},
|
||||
{
|
||||
location: {
|
||||
country_short: 'IT',
|
||||
region: 'Lazio',
|
||||
city: 'Rome',
|
||||
},
|
||||
expected: {
|
||||
key: 'IT:RM',
|
||||
label: 'Italy — Roma',
|
||||
},
|
||||
},
|
||||
{
|
||||
location: {
|
||||
country_short: 'PH',
|
||||
region: 'National Capital Region',
|
||||
city: 'Makati',
|
||||
},
|
||||
expected: {
|
||||
key: 'PH:00',
|
||||
label: 'Philippines — Metro Manila',
|
||||
},
|
||||
},
|
||||
] )(
|
||||
'should return the country option for location $expected',
|
||||
( { location, expected } ) => {
|
||||
expect(
|
||||
findCountryOption( countryStateOptions, location, 0.4 )
|
||||
).toEqual( expected );
|
||||
}
|
||||
);
|
||||
|
||||
it( 'should return a country option for > 98% locations', () => {
|
||||
let matchCount = 0;
|
||||
locations.forEach( ( location ) => {
|
||||
if ( findCountryOption( countryStateOptions, location, 0.4 ) ) {
|
||||
matchCount++;
|
||||
}
|
||||
} );
|
||||
expect( matchCount / locations.length ).toBeGreaterThan( 0.98 );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,41 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getMappingRegion } from '../../location-mapping';
|
||||
|
||||
describe( 'getMappingRegion', () => {
|
||||
it( 'should return null for an empty location', () => {
|
||||
expect( getMappingRegion( {} ) ).toBeNull();
|
||||
} );
|
||||
|
||||
it( 'should return null for a location that is not in the mapping', () => {
|
||||
expect(
|
||||
getMappingRegion( { country_short: 'US', region: 'California' } )
|
||||
).toBeNull();
|
||||
} );
|
||||
|
||||
it( 'should return null for a location with no region', () => {
|
||||
expect( getMappingRegion( { country_short: 'PH' } ) ).toBeNull();
|
||||
} );
|
||||
|
||||
it( 'should return the region for a location that is in the mapping with a region', () => {
|
||||
expect(
|
||||
getMappingRegion( {
|
||||
country_short: 'PH',
|
||||
region: 'National Capital Region',
|
||||
} )
|
||||
).toBe( 'Metro Manila' );
|
||||
} );
|
||||
|
||||
it( 'should return the region for a location that is in the mapping with a city', () => {
|
||||
expect(
|
||||
getMappingRegion( {
|
||||
country_short: 'IT',
|
||||
region: 'Lazio',
|
||||
city: 'Rome',
|
||||
} )
|
||||
).toBe( 'Roma' );
|
||||
} );
|
||||
} );
|
File diff suppressed because it is too large
Load Diff
|
@ -1949,10 +1949,22 @@ importers:
|
|||
gridicons:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0(react@17.0.2)
|
||||
string-similarity:
|
||||
specifier: 4.0.4
|
||||
version: 4.0.4
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
specifier: ^7.17.5
|
||||
version: 7.17.8
|
||||
'@testing-library/react':
|
||||
specifier: ^12.1.3
|
||||
version: 12.1.4(react-dom@16.14.0)(react@17.0.2)
|
||||
'@types/jest':
|
||||
specifier: ^27.4.1
|
||||
version: 27.4.1
|
||||
'@types/string-similarity':
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
'@types/wordpress__components':
|
||||
specifier: ^19.10.3
|
||||
version: 19.10.5(react-dom@16.14.0)(react@17.0.2)
|
||||
|
@ -1962,6 +1974,9 @@ importers:
|
|||
'@woocommerce/eslint-plugin':
|
||||
specifier: workspace:*
|
||||
version: link:../eslint-plugin
|
||||
'@woocommerce/internal-js-tests':
|
||||
specifier: workspace:*
|
||||
version: link:../internal-js-tests
|
||||
'@woocommerce/internal-style-build':
|
||||
specifier: workspace:*
|
||||
version: link:../internal-style-build
|
||||
|
@ -15537,6 +15552,20 @@ packages:
|
|||
react-error-boundary: 3.1.4(react@17.0.2)
|
||||
dev: true
|
||||
|
||||
/@testing-library/react@12.1.4(react-dom@16.14.0)(react@17.0.2):
|
||||
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
'@testing-library/dom': 8.11.3
|
||||
'@types/react-dom': 17.0.17
|
||||
react: 17.0.2
|
||||
react-dom: 16.14.0(react@17.0.2)
|
||||
dev: true
|
||||
|
||||
/@testing-library/react@12.1.4(react-dom@17.0.2)(react@17.0.2):
|
||||
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -16093,6 +16122,10 @@ packages:
|
|||
/@types/stack-utils@2.0.1:
|
||||
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
||||
|
||||
/@types/string-similarity@4.0.0:
|
||||
resolution: {integrity: sha512-dMS4S07fbtY1AILG/RhuwmptmzK1Ql8scmAebOTJ/8iBtK/KI17NwGwKzu1uipjj8Kk+3mfPxum56kKZE93mzQ==}
|
||||
dev: true
|
||||
|
||||
/@types/strip-ansi@3.0.0:
|
||||
resolution: {integrity: sha512-wVhzc+WJ/JNdV25MeaK0skxGdbdOFeqYv1sqY8yPXbsshZ0XwSbWWwfKzj836cPW+e+PpqUNvKoiac9ZqCdyRQ==}
|
||||
dev: false
|
||||
|
@ -20969,13 +21002,13 @@ packages:
|
|||
peerDependencies:
|
||||
eslint: '>= 4.12.1'
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.18.6
|
||||
'@babel/parser': 7.21.3
|
||||
'@babel/traverse': 7.21.3
|
||||
'@babel/types': 7.21.3
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@babel/parser': 7.17.8
|
||||
'@babel/traverse': 7.17.3
|
||||
'@babel/types': 7.17.0
|
||||
eslint: 7.32.0
|
||||
eslint-visitor-keys: 1.3.0
|
||||
resolve: 1.22.1
|
||||
resolve: 1.20.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -27360,7 +27393,7 @@ packages:
|
|||
vue-template-compiler:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.18.6
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@types/json-schema': 7.0.9
|
||||
chalk: 4.1.2
|
||||
chokidar: 3.5.3
|
||||
|
@ -40521,6 +40554,11 @@ packages:
|
|||
char-regex: 1.0.2
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
/string-similarity@4.0.4:
|
||||
resolution: {integrity: sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
dev: false
|
||||
|
||||
/string-template@0.2.1:
|
||||
resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==}
|
||||
dev: true
|
||||
|
|
Loading…
Reference in New Issue