From 89817d830b248577b1e0bf7156cee11d65823320 Mon Sep 17 00:00:00 2001 From: Mae Evans Date: Mon, 15 Jun 2026 14:22:07 -0600 Subject: [PATCH 1/4] Add US Autocomplete (V2) API client Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 2 +- Makefile | 2 + README.md | 1 + examples/us_autocomplete.mjs | 72 ++++++++++++++ examples/us_autocomplete.ts | 78 +++++++++++++++ index.ts | 11 +++ src/ClientBuilder.ts | 6 ++ src/us_autocomplete/Client.ts | 42 ++++++++ src/us_autocomplete/Lookup.ts | 46 +++++++++ src/us_autocomplete/Suggestion.ts | 38 +++++++ src/util/apiToSDKKeyMap.ts | 17 ++++ src/util/buildClients.ts | 5 + tests/us_autocomplete/test_Client.ts | 120 +++++++++++++++++++++++ tests/us_autocomplete/test_Lookup.ts | 18 ++++ tests/us_autocomplete/test_Suggestion.ts | 53 ++++++++++ 15 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 examples/us_autocomplete.mjs create mode 100644 examples/us_autocomplete.ts create mode 100644 src/us_autocomplete/Client.ts create mode 100644 src/us_autocomplete/Lookup.ts create mode 100644 src/us_autocomplete/Suggestion.ts create mode 100644 tests/us_autocomplete/test_Client.ts create mode 100644 tests/us_autocomplete/test_Lookup.ts create mode 100644 tests/us_autocomplete/test_Suggestion.ts diff --git a/CLAUDE.md b/CLAUDE.md index e3eaee41..7b45ce65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,7 +70,7 @@ Each API follows the same structure in `src//`: - `Client.ts` - Request/response handling - `Candidate.ts`, `Result.ts`, or `Suggestion.ts` - Response data structures -Supported APIs: `us_street`, `us_zipcode`, `us_autocomplete_pro`, `us_extract`, `us_enrichment`, `us_reverse_geo`, `international_street`, `international_address_autocomplete`, `international_postal_code` +Supported APIs: `us_street`, `us_zipcode`, `us_autocomplete`, `us_autocomplete_pro`, `us_extract`, `us_enrichment`, `us_reverse_geo`, `international_street`, `international_address_autocomplete`, `international_postal_code` ### us_enrichment specifics diff --git a/Makefile b/Makefile index 8946b02b..e6caa174 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ examples-ts: build @npx tsx examples/us_street_iana_timezone.ts || true @npx tsx examples/us_zipcode.ts || true @npx tsx examples/us_autocomplete_pro.ts || true + @npx tsx examples/us_autocomplete.ts || true @npx tsx examples/us_extract.ts || true @npx tsx examples/us_reverse_geo.ts || true @npx tsx examples/us_enrichment.ts || true @@ -44,6 +45,7 @@ examples-js: build @node examples/us_street_iana_timezone.mjs || true @node examples/us_zipcode.mjs || true @node examples/us_autocomplete_pro.mjs || true + @node examples/us_autocomplete.mjs || true @node examples/us_extract.mjs || true @node examples/us_reverse_geo.mjs || true @node examples/us_enrichment.mjs || true diff --git a/README.md b/README.md index 51ee0607..6e9f0619 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ console.log(response.result); // Array of address suggestions | [US Street](https://www.smarty.com/docs/cloud/us-street-api) | `usStreet` | `buildUsStreetApiClient()` | [example](examples/us_street.mjs) | | [US Zipcode](https://www.smarty.com/docs/cloud/us-zipcode-api) | `usZipcode` | `buildUsZipcodeClient()` | [example](examples/us_zipcode.mjs) | | [US Autocomplete Pro](https://www.smarty.com/docs/cloud/us-autocomplete-pro-api) | `usAutocompletePro` | `buildUsAutocompleteProClient()` | [example](examples/us_autocomplete_pro.mjs) | +| [US Autocomplete](https://www.smarty.com/docs/apis/us-autocomplete-v2) | `usAutocomplete` | `buildUsAutocompleteClient()` | [example](examples/us_autocomplete.mjs) | | [US Extract](https://www.smarty.com/docs/cloud/us-extract-api) | `usExtract` | `buildUsExtractClient()` | [example](examples/us_extract.mjs) | | [US Enrichment](https://www.smarty.com/docs/cloud/us-address-enrichment-api) | `usEnrichment` | `buildUsEnrichmentClient()` | [example](examples/us_enrichment.mjs) | | [US Reverse Geocoding](https://www.smarty.com/docs/cloud/us-reverse-geo-api) | `usReverseGeo` | `buildUsReverseGeoClient()` | [example](examples/us_reverse_geo.mjs) | diff --git a/examples/us_autocomplete.mjs b/examples/us_autocomplete.mjs new file mode 100644 index 00000000..2a9d1bc5 --- /dev/null +++ b/examples/us_autocomplete.mjs @@ -0,0 +1,72 @@ +import SmartySDK from "smartystreets-javascript-sdk"; + +// This example is for US Autocomplete (V2). It has the same name as a previous product +// which has been deprecated since 2022, which we refer to as US Autocomplete Basic. +// If you are still using US Autocomplete Basic, this SDK will not work. + +const SmartyCore = SmartySDK.core; +const Lookup = SmartySDK.usAutocomplete.Lookup; + +// for client-side requests (browser/mobile), use this code: +// let key = process.env.SMARTY_EMBEDDED_KEY; +// const credentials = new SmartyCore.SharedCredentials(key); + +// for Server-to-server requests, use this code: +let authId = process.env.SMARTY_AUTH_ID; +let authToken = process.env.SMARTY_AUTH_TOKEN; +const credentials = new SmartyCore.BasicAuthCredentials(authId, authToken); + +// The appropriate license values to be used for your subscriptions +// can be found on the Subscription page of the account dashboard. +// https://www.smarty.com/docs/cloud/licensing +let clientBuilder = new SmartyCore.ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API + +let client = clientBuilder.buildUsAutocompleteClient(); + +// Documentation for input fields can be found at: +// https://www.smarty.com/docs/apis/us-autocomplete-v2/reference#http-request-input-fields + +// *** Simple Lookup *** +let lookup = new Lookup("4770 Lincoln"); +// uncomment the following line to add a custom parameter +// lookup.addCustomParameter("max_results", 3); + +await handleRequest(lookup, "Simple Lookup"); + +// *** Using Filter and Prefer *** +lookup = new Lookup("4770 Lincoln"); + +lookup.maxResults = 10; +lookup.includeOnlyCities = ["Chicago,La Grange,IL", "Blaine,WA"]; +lookup.preferStates = ["IL"]; +lookup.preferRatio = 33; +lookup.source = "all"; + +await handleRequest(lookup, "Using Filter and Prefer"); + +// *** Using 'selected' to Expand Secondaries *** +// Take an entry_id from a previous result that has secondaries and pass it back as the selected address. +const entryId = lookup.result.find((suggestion) => suggestion.entryId)?.entryId; +if (entryId) { + lookup = new Lookup("4770 Lincoln"); + lookup.selected = entryId; + await handleRequest(lookup, "Using 'selected' to Expand Secondaries"); +} + +// ************************************************ + +function logSuggestions(response, message) { + console.log(message); + console.log(response.result); + console.log("*********************"); +} + +async function handleRequest(lookup, lookupType) { + try { + const results = await client.send(lookup); + logSuggestions(results, lookupType); + } catch (err) { + console.log(err); + } +} diff --git a/examples/us_autocomplete.ts b/examples/us_autocomplete.ts new file mode 100644 index 00000000..7e0d2baf --- /dev/null +++ b/examples/us_autocomplete.ts @@ -0,0 +1,78 @@ +import { + ClientBuilder, + BasicAuthCredentials, + LookupUSAutocomplete, +} from "smartystreets-javascript-sdk"; + +// This example is for US Autocomplete (V2). It has the same name as a previous product +// which has been deprecated since 2022, which we refer to as US Autocomplete Basic. +// If you are still using US Autocomplete Basic, this SDK will not work. + +// for client-side requests (browser/mobile), use this code: +// import { SharedCredentials } from "smartystreets-javascript-sdk"; +// const key: string = process.env.SMARTY_EMBEDDED_KEY!; +// const credentials = new SharedCredentials(key); + +// for Server-to-server requests, use this code: +const authId = process.env.SMARTY_AUTH_ID!; +const authToken = process.env.SMARTY_AUTH_TOKEN!; +const credentials = new BasicAuthCredentials(authId, authToken); + +// The appropriate license values to be used for your subscriptions +// can be found on the Subscription page of the account dashboard. +// https://www.smarty.com/docs/cloud/licensing +const clientBuilder = new ClientBuilder(credentials); +// .withBaseUrl("YOUR URL") // withBaseUrl() should be used if you are self-hosting the Smarty API + +const client = clientBuilder.buildUsAutocompleteClient(); + +// Documentation for input fields can be found at: +// https://www.smarty.com/docs/apis/us-autocomplete-v2/reference#http-request-input-fields + +// ************************************************ + +function logSuggestions(response: LookupUSAutocomplete, message: string): void { + console.log(message); + console.log(response.result); + console.log("*********************"); +} + +async function handleRequest(lookup: LookupUSAutocomplete, lookupType: string): Promise { + try { + const results = await client.send(lookup); + logSuggestions(results, lookupType); + } catch (err: unknown) { + console.error(err); + } +} + +async function main(): Promise { + // *** Simple Lookup *** + let lookup = new LookupUSAutocomplete("4770 Lincoln"); + // uncomment the following line to add a custom parameter + // lookup.addCustomParameter("max_results", "3"); + + await handleRequest(lookup, "Simple Lookup"); + + // *** Using Filter and Prefer *** + lookup = new LookupUSAutocomplete("4770 Lincoln"); + + lookup.maxResults = 10; + lookup.includeOnlyCities = ["Chicago,La Grange,IL", "Blaine,WA"]; + lookup.preferStates = ["IL"]; + lookup.preferRatio = 33; + lookup.source = "all"; + + await handleRequest(lookup, "Using Filter and Prefer"); + + // *** Using 'selected' to Expand Secondaries *** + // Take an entry_id from a previous result that has secondaries and pass it back as the selected address. + const entryId = lookup.result.find((suggestion) => suggestion.entryId)?.entryId; + if (entryId) { + lookup = new LookupUSAutocomplete("4770 Lincoln"); + lookup.selected = entryId; + await handleRequest(lookup, "Using 'selected' to Expand Secondaries"); + } +} + +main(); diff --git a/index.ts b/index.ts index edb29cd3..70d9bba1 100644 --- a/index.ts +++ b/index.ts @@ -19,6 +19,9 @@ import ResultUSZipcode from "./src/us_zipcode/Result.js"; import LookupUSAutocompletePro from "./src/us_autocomplete_pro/Lookup.js"; import SuggestionUSAutocompletePro from "./src/us_autocomplete_pro/Suggestion.js"; +import LookupUSAutocomplete from "./src/us_autocomplete/Lookup.js"; +import SuggestionUSAutocomplete from "./src/us_autocomplete/Suggestion.js"; + import LookupUSExtract from "./src/us_extract/Lookup.js"; import ResultUSExtract from "./src/us_extract/Result.js"; @@ -70,6 +73,8 @@ export { ResultUSZipcode, LookupUSAutocompletePro, SuggestionUSAutocompletePro, + LookupUSAutocomplete, + SuggestionUSAutocomplete, LookupUSExtract, ResultUSExtract, LookupInternationalStreet, @@ -119,6 +124,11 @@ export const usAutocompletePro = { Suggestion: SuggestionUSAutocompletePro, }; +export const usAutocomplete = { + Lookup: LookupUSAutocomplete, + Suggestion: SuggestionUSAutocomplete, +}; + export const usExtract = { Lookup: LookupUSExtract, Result: ResultUSExtract, @@ -164,6 +174,7 @@ export default { usStreet, usZipcode, usAutocompletePro, + usAutocomplete, usExtract, internationalStreet, usReverseGeo, diff --git a/src/ClientBuilder.ts b/src/ClientBuilder.ts index 0e9e1213..8c9b5599 100644 --- a/src/ClientBuilder.ts +++ b/src/ClientBuilder.ts @@ -16,6 +16,7 @@ import Sleeper from "./util/Sleeper.js"; import UsStreetClient from "./us_street/Client.js"; import UsZipcodeClient from "./us_zipcode/Client.js"; import UsAutocompleteProClient from "./us_autocomplete_pro/Client.js"; +import UsAutocompleteClient from "./us_autocomplete/Client.js"; import UsExtractClient from "./us_extract/Client.js"; import InternationalStreetClient from "./international_street/Client.js"; import UsReverseGeoClient from "./us_reverse_geo/Client.js"; @@ -26,6 +27,7 @@ import { Sender } from "./types.js"; const INTERNATIONAL_STREET_API_URI = "https://international-street.api.smarty.com/verify"; const US_AUTOCOMPLETE_PRO_API_URL = "https://us-autocomplete-pro.api.smarty.com/lookup"; +const US_AUTOCOMPLETE_API_URL = "https://us-autocomplete.api.smarty.com/v2/lookup"; const US_EXTRACT_API_URL = "https://us-extract.api.smarty.com/"; const US_STREET_API_URL = "https://us-street.api.smarty.com/street-address"; const US_ZIP_CODE_API_URL = "https://us-zipcode.api.smarty.com/lookup"; @@ -242,6 +244,10 @@ export default class ClientBuilder { return this.buildClient(US_AUTOCOMPLETE_PRO_API_URL, UsAutocompleteProClient); } + buildUsAutocompleteClient(): UsAutocompleteClient { + return this.buildClient(US_AUTOCOMPLETE_API_URL, UsAutocompleteClient); + } + buildUsExtractClient(): UsExtractClient { return this.buildClient(US_EXTRACT_API_URL, UsExtractClient); } diff --git a/src/us_autocomplete/Client.ts b/src/us_autocomplete/Client.ts new file mode 100644 index 00000000..588817a6 --- /dev/null +++ b/src/us_autocomplete/Client.ts @@ -0,0 +1,42 @@ +import { UndefinedLookupError } from "../Errors.js"; +import Request from "../Request.js"; +import Suggestion, { RawUsAutocompleteSuggestion } from "./Suggestion.js"; +import buildInputData from "../util/buildInputData.js"; +import apiToSDKKeyMap from "../util/apiToSDKKeyMap.js"; +import { Sender } from "../types.js"; +import Lookup from "./Lookup.js"; + +const keyTranslationFormat = apiToSDKKeyMap.usAutocomplete; + +export default class Client { + private sender: Sender; + + constructor(sender: Sender) { + this.sender = sender; + } + + send(lookup: Lookup): Promise { + if (typeof lookup === "undefined") throw new UndefinedLookupError(); + + const request = new Request(); + request.parameters = buildInputData(lookup, keyTranslationFormat); + + return new Promise((resolve, reject) => { + this.sender + .send(request) + .then((response) => { + if (response.error) return reject(response.error); + + const payload = response.payload as { + suggestions: RawUsAutocompleteSuggestion[] | null; + }; + lookup.result = + payload.suggestions === null + ? [] + : payload.suggestions.map((suggestion) => new Suggestion(suggestion)); + resolve(lookup); + }) + .catch(reject); + }); + } +} diff --git a/src/us_autocomplete/Lookup.ts b/src/us_autocomplete/Lookup.ts new file mode 100644 index 00000000..2f6240fb --- /dev/null +++ b/src/us_autocomplete/Lookup.ts @@ -0,0 +1,46 @@ +import Suggestion from "./Suggestion.js"; + +export type Geolocation = "city" | "none" | (string & {}); + +export default class Lookup { + result: Suggestion[]; + search: string | undefined; + selected: string | undefined; + exclude: string | undefined; + maxResults: number | undefined; + includeOnlyCities: string[]; + includeOnlyStates: string[]; + includeOnlyZIPCodes: string[]; + excludeStates: string[]; + preferCities: string[]; + preferStates: string[]; + preferZIPCodes: string[]; + preferRatio: number | undefined; + preferGeolocation: Geolocation | undefined; + source: string | undefined; + customParameters: Record; + + constructor(search?: string) { + this.result = []; + + this.search = search; + this.selected = undefined; + this.exclude = undefined; + this.maxResults = undefined; + this.includeOnlyCities = []; + this.includeOnlyStates = []; + this.includeOnlyZIPCodes = []; + this.excludeStates = []; + this.preferCities = []; + this.preferStates = []; + this.preferZIPCodes = []; + this.preferRatio = undefined; + this.preferGeolocation = undefined; + this.source = undefined; + this.customParameters = {}; + } + + addCustomParameter(key: string, value: string): void { + this.customParameters[key] = value; + } +} diff --git a/src/us_autocomplete/Suggestion.ts b/src/us_autocomplete/Suggestion.ts new file mode 100644 index 00000000..39d2e97e --- /dev/null +++ b/src/us_autocomplete/Suggestion.ts @@ -0,0 +1,38 @@ +export interface RawUsAutocompleteSuggestion { + smarty_key?: string; + entry_id?: string; + street_line?: string; + secondary?: string; + city?: string; + state?: string; + zipcode?: string; + entries?: number; + source?: string; +} + +export default class Suggestion { + smartyKey: string; + entryId: string; + streetLine: string; + secondary: string; + city: string; + state: string; + zipcode: string; + entries: number; + source: string | undefined; + + constructor(responseData: RawUsAutocompleteSuggestion) { + this.smartyKey = responseData.smarty_key ?? ""; + this.entryId = responseData.entry_id ?? ""; + this.streetLine = responseData.street_line ?? ""; + this.secondary = responseData.secondary ?? ""; + this.city = responseData.city ?? ""; + this.state = responseData.state ?? ""; + this.zipcode = responseData.zipcode ?? ""; + this.entries = responseData.entries ?? 0; + + if (responseData.source) { + this.source = responseData.source; + } + } +} diff --git a/src/util/apiToSDKKeyMap.ts b/src/util/apiToSDKKeyMap.ts index e76e3f99..2255cc65 100644 --- a/src/util/apiToSDKKeyMap.ts +++ b/src/util/apiToSDKKeyMap.ts @@ -1,6 +1,7 @@ interface ApiToSDKKeyMap { usStreet: Record; usAutocompletePro: Record; + usAutocomplete: Record; usZipcode: Record; internationalStreet: Record; internationalAddressAutocomplete: Record; @@ -42,6 +43,22 @@ const apiToSDKKeyMap: ApiToSDKKeyMap = { prefer_geolocation: "preferGeolocation", source: "source", }, + usAutocomplete: { + search: "search", + selected: "selected", + exclude: "exclude", + max_results: "maxResults", + include_only_cities: "includeOnlyCities", + include_only_states: "includeOnlyStates", + include_only_zip_codes: "includeOnlyZIPCodes", + exclude_states: "excludeStates", + prefer_cities: "preferCities", + prefer_states: "preferStates", + prefer_zip_codes: "preferZIPCodes", + prefer_ratio: "preferRatio", + prefer_geolocation: "preferGeolocation", + source: "source", + }, usZipcode: { city: "city", state: "state", diff --git a/src/util/buildClients.ts b/src/util/buildClients.ts index 6f2ad556..52a44d31 100644 --- a/src/util/buildClients.ts +++ b/src/util/buildClients.ts @@ -17,6 +17,10 @@ function buildUsAutocompleteProApiClient(credentials: Credentials) { return instantiateClientBuilder(credentials).buildUsAutocompleteProClient(); } +function buildUsAutocompleteApiClient(credentials: Credentials) { + return instantiateClientBuilder(credentials).buildUsAutocompleteClient(); +} + function buildUsExtractApiClient(credentials: Credentials) { return instantiateClientBuilder(credentials).buildUsExtractClient(); } @@ -48,6 +52,7 @@ function buildInternationalPostalCodeApiClient(credentials: Credentials) { export default { usStreet: buildUsStreetApiClient, usAutocompletePro: buildUsAutocompleteProApiClient, + usAutocomplete: buildUsAutocompleteApiClient, usExtract: buildUsExtractApiClient, usZipcode: buildUsZipcodeApiClient, internationalStreet: buildInternationalStreetApiClient, diff --git a/tests/us_autocomplete/test_Client.ts b/tests/us_autocomplete/test_Client.ts new file mode 100644 index 00000000..3e67a249 --- /dev/null +++ b/tests/us_autocomplete/test_Client.ts @@ -0,0 +1,120 @@ +import { expect } from "chai"; +import Client from "../../src/us_autocomplete/Client.js"; +import Lookup from "../../src/us_autocomplete/Lookup.js"; +import Suggestion from "../../src/us_autocomplete/Suggestion.js"; +import errors from "../../src/Errors.js"; +import { MockSender, MockSenderWithResponse } from "../fixtures/mock_senders.js"; + +describe("A US Autocomplete Client", function () { + it("correctly builds parameters for a search only lookup.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + let search = '(>")>#'; + let lookup = new Lookup(search); + let expectedParameters = { + exclude_states: "", + include_only_cities: "", + include_only_states: "", + include_only_zip_codes: "", + prefer_cities: "", + prefer_states: "", + prefer_zip_codes: "", + search: search, + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("correctly builds parameters for a fully-populated lookup.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + let lookup = new Lookup(); + lookup.search = "1"; + lookup.selected = "2"; + lookup.exclude = "u"; + lookup.maxResults = "3" as any; + lookup.includeOnlyCities = ["a,b", "c,d"]; + lookup.includeOnlyStates = ["e", "f"]; + lookup.includeOnlyZIPCodes = ["g", "h"]; + lookup.excludeStates = ["i", "j"]; + lookup.preferCities = ["k,l", "m,n"]; + lookup.preferStates = ["o", "p"]; + lookup.preferZIPCodes = ["q", "r"]; + lookup.preferRatio = "s" as any; + lookup.preferGeolocation = "t"; + lookup.source = "all"; + + let expectedParameters = { + search: "1", + selected: "2", + exclude: "u", + max_results: "3", + include_only_cities: "a,b;c,d", + include_only_states: "e;f", + include_only_zip_codes: "g;h", + exclude_states: "i;j", + prefer_cities: "k,l;m,n", + prefer_states: "o;p", + prefer_zip_codes: "q;r", + prefer_ratio: "s", + prefer_geolocation: "t", + source: "all", + }; + + client.send(lookup); + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("throws an error if sending without a lookup.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + expect(client.send).to.throw(errors.UndefinedLookupError); + }); + + it("rejects with an exception if the response comes back with an error.", function () { + let expectedError = new Error("I'm the error."); + let mockSender = new MockSenderWithResponse("", expectedError); + let client = new Client(mockSender); + let lookup = new Lookup("¯\\_(ツ)_/¯"); + + return client.send(lookup).catch((e) => { + expect(e).to.equal(expectedError); + }); + }); + + it("returns an empty array when no suggestions are returned.", () => { + let mockExpectedPayload = { suggestions: null }; + let mockSender = new MockSenderWithResponse(mockExpectedPayload); + let client = new Client(mockSender); + let lookup = new Lookup("Please let this be easy to test."); + let expectedSuggestion: any[] = []; + + return client.send(lookup).then((_response) => { + expect(lookup.result).to.deep.equal(expectedSuggestion); + }); + }); + + it("attaches suggestions from a response to a lookup.", function () { + const responseData = { + smarty_key: "z", + entry_id: "y", + street_line: "a", + secondary: "b", + city: "c", + state: "d", + zipcode: "e", + entries: 6, + }; + let mockExpectedPayload = { suggestions: [responseData] }; + let mockSender = new MockSenderWithResponse(mockExpectedPayload); + let client = new Client(mockSender); + let lookup = new Lookup("Trevor the Vampire"); + let expectedSuggestion = new Suggestion(responseData); + + return client.send(lookup).then((_response) => { + expect(lookup.result[0]).to.deep.equal(expectedSuggestion); + }); + }); +}); diff --git a/tests/us_autocomplete/test_Lookup.ts b/tests/us_autocomplete/test_Lookup.ts new file mode 100644 index 00000000..19752ef8 --- /dev/null +++ b/tests/us_autocomplete/test_Lookup.ts @@ -0,0 +1,18 @@ +import { expect } from "chai"; +import Lookup from "../../src/us_autocomplete/Lookup.js"; + +describe("A US Autocomplete Lookup", function () { + it("can be newed up with a search.", function () { + const expectedSearch = "a"; + let lookup = new Lookup(expectedSearch); + expect(lookup.search).to.equal(expectedSearch); + }); + + it("defaults exclude to undefined and lets it be set.", function () { + let lookup = new Lookup("a"); + expect(lookup.exclude).to.equal(undefined); + + lookup.exclude = "123 Main St"; + expect(lookup.exclude).to.equal("123 Main St"); + }); +}); diff --git a/tests/us_autocomplete/test_Suggestion.ts b/tests/us_autocomplete/test_Suggestion.ts new file mode 100644 index 00000000..3548aad4 --- /dev/null +++ b/tests/us_autocomplete/test_Suggestion.ts @@ -0,0 +1,53 @@ +import { expect } from "chai"; +import Suggestion from "../../src/us_autocomplete/Suggestion.js"; + +describe("A US Autocomplete Suggestion", function () { + it("is initialized correctly with API response data.", function () { + const mockSuggestion = { + smarty_key: "z", + entry_id: "y", + street_line: "a", + secondary: "b", + city: "c", + state: "d", + zipcode: "e", + entries: 6, + }; + let suggestion = new Suggestion(mockSuggestion); + + expect(suggestion.smartyKey).to.equal("z"); + expect(suggestion.entryId).to.equal("y"); + expect(suggestion.streetLine).to.equal("a"); + expect(suggestion.secondary).to.equal("b"); + expect(suggestion.city).to.equal("c"); + expect(suggestion.state).to.equal("d"); + expect(suggestion.zipcode).to.equal("e"); + expect(suggestion.entries).to.equal(6); + }); + + it("defaults smartyKey and entryId to empty strings when absent from the response.", function () { + const mockSuggestion = { + street_line: "a", + city: "c", + state: "d", + zipcode: "e", + }; + let suggestion = new Suggestion(mockSuggestion); + + expect(suggestion.smartyKey).to.equal(""); + expect(suggestion.entryId).to.equal(""); + expect(suggestion.entries).to.equal(0); + }); + + it("populates smartyKey and entryId from the response.", function () { + let suggestion = new Suggestion({ smarty_key: "z", entry_id: "y" }); + + expect(suggestion.smartyKey).to.equal("z"); + expect(suggestion.entryId).to.equal("y"); + }); + + it("sets source only when present in the response.", function () { + expect(new Suggestion({}).source).to.equal(undefined); + expect(new Suggestion({ source: "postal" }).source).to.equal("postal"); + }); +}); From e91625e419527bb119d497aa5d4d6f0481ff64f1 Mon Sep 17 00:00:00 2001 From: Andrea Wait Date: Thu, 18 Jun 2026 13:45:52 -0600 Subject: [PATCH 2/4] refactored source to const --- index.ts | 1 + src/us_autocomplete/Lookup.ts | 3 +- tests/us_autocomplete/test_Client.ts | 67 ++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/index.ts b/index.ts index 70d9bba1..e322bae8 100644 --- a/index.ts +++ b/index.ts @@ -57,6 +57,7 @@ export { SmartyError, NotModifiedError } from "./src/Errors.js"; export type { MatchStrategy, OutputFormat, CountySource } from "./src/us_street/Lookup.js"; export type { CoordinateLicense, MatchInfo } from "./src/us_street/Candidate.js"; export type { Geolocation } from "./src/us_autocomplete_pro/Lookup.js"; +export type { AutocompleteSource } from "./src/us_autocomplete/Lookup.js"; export type { Language, Geocode } from "./src/international_street/Lookup.js"; export { diff --git a/src/us_autocomplete/Lookup.ts b/src/us_autocomplete/Lookup.ts index 2f6240fb..ef1b39d6 100644 --- a/src/us_autocomplete/Lookup.ts +++ b/src/us_autocomplete/Lookup.ts @@ -1,6 +1,7 @@ import Suggestion from "./Suggestion.js"; export type Geolocation = "city" | "none" | (string & {}); +export type AutocompleteSource = "all" | "postal" | (string & {}); export default class Lookup { result: Suggestion[]; @@ -17,7 +18,7 @@ export default class Lookup { preferZIPCodes: string[]; preferRatio: number | undefined; preferGeolocation: Geolocation | undefined; - source: string | undefined; + source: AutocompleteSource | undefined; customParameters: Record; constructor(search?: string) { diff --git a/tests/us_autocomplete/test_Client.ts b/tests/us_autocomplete/test_Client.ts index 3e67a249..f8ce6c71 100644 --- a/tests/us_autocomplete/test_Client.ts +++ b/tests/us_autocomplete/test_Client.ts @@ -67,6 +67,73 @@ describe("A US Autocomplete Client", function () { expect(mockSender.request.parameters).to.deep.equal(expectedParameters); }); + it("correctly builds parameters with source all.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + let lookup = new Lookup("1"); + lookup.source = "all"; + + let expectedParameters = { + exclude_states: "", + include_only_cities: "", + include_only_states: "", + include_only_zip_codes: "", + prefer_cities: "", + prefer_states: "", + prefer_zip_codes: "", + search: "1", + source: "all", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("correctly builds parameters with source postal.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + let lookup = new Lookup("1"); + lookup.source = "postal"; + + let expectedParameters = { + exclude_states: "", + include_only_cities: "", + include_only_states: "", + include_only_zip_codes: "", + prefer_cities: "", + prefer_states: "", + prefer_zip_codes: "", + search: "1", + source: "postal", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + + it("correctly builds parameters with no source.", function () { + let mockSender = new MockSender(); + let client = new Client(mockSender); + let lookup = new Lookup("1"); + + let expectedParameters = { + exclude_states: "", + include_only_cities: "", + include_only_states: "", + include_only_zip_codes: "", + prefer_cities: "", + prefer_states: "", + prefer_zip_codes: "", + search: "1", + }; + + client.send(lookup); + + expect(mockSender.request.parameters).to.deep.equal(expectedParameters); + }); + it("throws an error if sending without a lookup.", function () { let mockSender = new MockSender(); let client = new Client(mockSender); From 4d2ca598b964118a7a46fdd904c72d8f4e8ecbdd Mon Sep 17 00:00:00 2001 From: Mae Evans Date: Mon, 22 Jun 2026 13:20:18 -0600 Subject: [PATCH 3/4] standardizing source constructing --- src/us_autocomplete/Suggestion.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/us_autocomplete/Suggestion.ts b/src/us_autocomplete/Suggestion.ts index 39d2e97e..b85c18f7 100644 --- a/src/us_autocomplete/Suggestion.ts +++ b/src/us_autocomplete/Suggestion.ts @@ -19,7 +19,7 @@ export default class Suggestion { state: string; zipcode: string; entries: number; - source: string | undefined; + source: string; constructor(responseData: RawUsAutocompleteSuggestion) { this.smartyKey = responseData.smarty_key ?? ""; @@ -30,9 +30,6 @@ export default class Suggestion { this.state = responseData.state ?? ""; this.zipcode = responseData.zipcode ?? ""; this.entries = responseData.entries ?? 0; - - if (responseData.source) { - this.source = responseData.source; - } + this.source = responseData.source ?? ""; } } From ff70ee6a0779723bebf30e5b72e59896e2a46384 Mon Sep 17 00:00:00 2001 From: Mae Evans Date: Mon, 22 Jun 2026 13:27:22 -0600 Subject: [PATCH 4/4] tests updated to match source changes --- tests/us_autocomplete/test_Suggestion.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/us_autocomplete/test_Suggestion.ts b/tests/us_autocomplete/test_Suggestion.ts index 3548aad4..17c2dc69 100644 --- a/tests/us_autocomplete/test_Suggestion.ts +++ b/tests/us_autocomplete/test_Suggestion.ts @@ -12,6 +12,7 @@ describe("A US Autocomplete Suggestion", function () { state: "d", zipcode: "e", entries: 6, + source: "f", }; let suggestion = new Suggestion(mockSuggestion); @@ -23,9 +24,10 @@ describe("A US Autocomplete Suggestion", function () { expect(suggestion.state).to.equal("d"); expect(suggestion.zipcode).to.equal("e"); expect(suggestion.entries).to.equal(6); + expect(suggestion.source).to.equal("f"); }); - it("defaults smartyKey and entryId to empty strings when absent from the response.", function () { + it("defaults smartyKey, entryId, and source to empty strings when absent from the response.", function () { const mockSuggestion = { street_line: "a", city: "c", @@ -37,6 +39,7 @@ describe("A US Autocomplete Suggestion", function () { expect(suggestion.smartyKey).to.equal(""); expect(suggestion.entryId).to.equal(""); expect(suggestion.entries).to.equal(0); + expect(suggestion.source).to.equal(""); }); it("populates smartyKey and entryId from the response.", function () { @@ -45,9 +48,4 @@ describe("A US Autocomplete Suggestion", function () { expect(suggestion.smartyKey).to.equal("z"); expect(suggestion.entryId).to.equal("y"); }); - - it("sets source only when present in the response.", function () { - expect(new Suggestion({}).source).to.equal(undefined); - expect(new Suggestion({ source: "postal" }).source).to.equal("postal"); - }); });