diff --git a/src/ui/city-autocomplete/CityAutocomplete.test.tsx b/src/ui/city-autocomplete/CityAutocomplete.test.tsx
index 51eec265..65f265d2 100644
--- a/src/ui/city-autocomplete/CityAutocomplete.test.tsx
+++ b/src/ui/city-autocomplete/CityAutocomplete.test.tsx
@@ -150,6 +150,60 @@ describe("CityAutocomplete", () => {
expect(container.querySelector(".city-autocomplete__input--has-error")).toBeTruthy();
});
+ it("auto-commits a typed city name that exactly matches a dictionary entry", () => {
+ const onChange = vi.fn();
+ render(
+ ,
+ );
+ fireEvent.change(screen.getByTestId("test-input"), {
+ target: { value: "Москва" },
+ });
+ expect(onChange).toHaveBeenCalledWith("MOW");
+ });
+
+ it("auto-commits a typed airport name to its owning city code", () => {
+ const onChange = vi.fn();
+ render(
+ ,
+ );
+ fireEvent.change(screen.getByTestId("test-input"), {
+ target: { value: "Шереметьево" },
+ });
+ expect(onChange).toHaveBeenCalledWith("MOW");
+ });
+
+ it("does not auto-commit partial typed text", () => {
+ const onChange = vi.fn();
+ render(
+ ,
+ );
+ fireEvent.change(screen.getByTestId("test-input"), {
+ target: { value: "Мос" },
+ });
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
it("does not open popup when dictionaries is null", () => {
render(
= ({
return () => document.removeEventListener("mousedown", handler);
}, []);
+ // Auto-commit free-typed text that exactly matches a city or airport
+ // name. Without this, a user who types "Москва" and clicks the search
+ // button without picking from the dropdown silently fails: the
+ // parent-held `value` (city code) stays empty while the AutoComplete's
+ // local input holds the typed string. Exact name match (ru + en) is
+ // safe — partial text is ignored so the picker still wins for
+ // disambiguation.
+ useEffect(() => {
+ if (!dictionaries || value) return;
+ if (typeof inputValue !== "string") return;
+ const trimmed = inputValue.trim();
+ if (!trimmed) return;
+ const lc = trimmed.toLowerCase();
+ const cityMatch = dictionaries.cities.find(
+ (c) => c.name.toLowerCase() === lc,
+ );
+ if (cityMatch) {
+ onChange(cityMatch.code);
+ return;
+ }
+ const airportMatch = dictionaries.airports.find(
+ (a) => a.name.toLowerCase() === lc,
+ );
+ if (airportMatch) {
+ onChange(airportMatch.city_code.toUpperCase());
+ }
+ }, [inputValue, dictionaries, value, onChange]);
+
const handleComplete = useCallback(
(event: AutoCompleteCompleteEvent) => {
if (!dictionaries) {