diff --git a/src/components/Search/hooks/useSearchKeyParam.ts b/src/components/Search/hooks/useSearchKeyParam.ts index c23bcedfb4a5..e7f2ac0f18ec 100644 --- a/src/components/Search/hooks/useSearchKeyParam.ts +++ b/src/components/Search/hooks/useSearchKeyParam.ts @@ -7,7 +7,7 @@ import useRootNavigationState from '@hooks/useRootNavigationState'; import Navigation, {getDeepestFocusedScreen} from '@libs/Navigation/Navigation'; import type {SearchKey} from '@libs/SearchKeyUtils'; import {getSearchKeyForDataType, isExistingSearchKey, savedSearchIDToSearchKey, searchKeyToSavedSearchID} from '@libs/SearchKeyUtils'; -import {buildSearchQueryJSON, doesQueryMatchDefaultFilterKeysAndType} from '@libs/SearchQueryUtils'; +import {buildSearchQueryJSON, doesQueryMatchDefaultFilterKeysAndType, normalizeDatePresetsInQuery} from '@libs/SearchQueryUtils'; import type {SearchTypeMenuItem} from '@libs/SearchUIUtils'; import {getLastSearchQuery} from '@libs/SearchUIUtils'; @@ -72,7 +72,7 @@ function useSearchKeyParam(currentSearchQueryJSON: SearchQueryJSON | undefined, const lastSavedSearchQuery = getLastSearchQuery(searchFilters, savedSearchIDToSearchKey(id)); return ( - (savedSearchQuery ? buildSearchQueryJSON(savedSearchQuery)?.hash === queryJSON?.hash : false) || + (savedSearchQuery ? buildSearchQueryJSON(normalizeDatePresetsInQuery(savedSearchQuery))?.hash === queryJSON?.hash : false) || (lastSavedSearchQuery ? buildSearchQueryJSON(lastSavedSearchQuery)?.hash === queryJSON?.hash : false) ); }); @@ -91,7 +91,7 @@ function useSearchKeyParam(currentSearchQueryJSON: SearchQueryJSON | undefined, const savedSearchID = searchKeyToSavedSearchID(searchKey); const defaultSearchQueryString = savedSearchID ? savedSearches?.[savedSearchID]?.query : suggestedSearches[searchKey]?.searchQuery; - return defaultSearchQueryString ? buildSearchQueryJSON(defaultSearchQueryString) : undefined; + return defaultSearchQueryString ? buildSearchQueryJSON(normalizeDatePresetsInQuery(defaultSearchQueryString)) : undefined; }; // The `searchKey` param is sticky: it survives a `Navigation.setParams({q})`, so tweaking a filter keeps the diff --git a/src/languages/de.ts b/src/languages/de.ts index c7ea8d0d5807..62a822ef8f2e 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -10745,7 +10745,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nie', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Letzter Monat', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Diesen Monat', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Laufendes Jahr', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Dieses Jahr', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Letzte 12 Monate', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Diese Woche', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'Letzte Woche', diff --git a/src/languages/el.ts b/src/languages/el.ts index 05a4b0d891aa..b014be8ee074 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -10953,7 +10953,7 @@ ${reportName}`, [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Ποτέ', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Τον προηγούμενο μήνα', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Αυτόν τον μήνα', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Έως σήμερα μέσα στο έτος', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Φέτος', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Τελευταίοι 12 μήνες', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Αυτή την εβδομάδα', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'Την προηγούμενη εβδομάδα', diff --git a/src/languages/en.ts b/src/languages/en.ts index 6d8208ee233c..f10714b875e0 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -11013,7 +11013,7 @@ const translations = { [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Never', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Last month', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'This month', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Year to date', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'This year', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Last 12 months', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'This week', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'Last week', diff --git a/src/languages/es.ts b/src/languages/es.ts index e2997c44b545..4a61140b4449 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -10599,7 +10599,7 @@ ${reportName}`, [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nunca', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'El mes pasado', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Este mes', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Año hasta la fecha', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Este año', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Últimos 12 meses', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Esta semana', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'La semana pasada', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 50358123378a..1993c812fff4 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -10772,7 +10772,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Jamais', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Mois dernier', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Ce mois-ci', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Depuis le début de l’année', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Cette année', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: '12 derniers mois', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Cette semaine', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'Semaine dernière', diff --git a/src/languages/it.ts b/src/languages/it.ts index c9a293f0a292..53288afaf3e1 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -10698,7 +10698,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Mai', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Il mese scorso', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Questo mese', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Da inizio anno', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Quest’anno', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Ultimi 12 mesi', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Questa settimana', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'La settimana scorsa', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index a2f6cc6054d2..0418c9f817c7 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -10542,7 +10542,7 @@ ${reportName}`, [CONST.SEARCH.DATE_PRESETS.NEVER]: '決してない', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: '先月', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: '今月', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: '年初来(累計)', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: '今年', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: '過去12か月', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: '今週', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: '先週', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 9723637e0ad0..72fcebb27c44 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -10667,7 +10667,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nooit', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Vorige maand', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Deze maand', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Jaar tot nu toe', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Dit jaar', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Laatste 12 maanden', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Deze week', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'Vorige week', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index acac974e8a83..4c9aa569661f 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -10689,7 +10689,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nigdy', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'W zeszłym miesiącu', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Ten miesiąc', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Od początku roku', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Ten rok', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Ostatnie 12 miesięcy', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Ten tydzień', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'W zeszłym tygodniu', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 72d4ca619210..be0e76414b78 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -10669,7 +10669,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, [CONST.SEARCH.DATE_PRESETS.NEVER]: 'Nunca', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: 'Mês passado', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: 'Este mês', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Ano até agora', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: 'Este ano', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: 'Últimos 12 meses', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: 'Esta semana', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: 'Semana passada', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d853af9d3c81..9ec19fb7618c 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -10271,7 +10271,7 @@ ${reportName}`, [CONST.SEARCH.DATE_PRESETS.NEVER]: '从不', [CONST.SEARCH.DATE_PRESETS.LAST_MONTH]: '上个月', [CONST.SEARCH.DATE_PRESETS.THIS_MONTH]: '本月', - [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: '本年度至今', + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE]: '今年', [CONST.SEARCH.DATE_PRESETS.LAST_12_MONTHS]: '过去 12 个月', [CONST.SEARCH.DATE_PRESETS.THIS_WEEK]: '本周', [CONST.SEARCH.DATE_PRESETS.LAST_WEEK]: '上周', diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index cff07de4ac5b..8a8b67641005 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -964,7 +964,9 @@ function getQueryHashWithoutFilters(query: SearchQueryJSON, exclude: ReadonlySet const flatFilters = query.flatFilters .map((filter) => { const filterKey = filter.key; - const filters = cloneDeep(filter.filters); + const filters = cloneDeep(filter.filters).map((queryFilter) => + canFilterValueBeDatePreset(filterKey, queryFilter.operator) ? {...queryFilter, value: normalizeDatePreset(queryFilter.value.toString())} : queryFilter, + ); filters.sort((a, b) => customCollator.compare(a.value.toString(), b.value.toString())); return {filterString: buildFilterValuesString(filterKey, filters), filterKey}; }) @@ -1080,6 +1082,45 @@ function isSearchDatePreset(date: string | undefined): date is SearchDatePreset return Object.values(CONST.SEARCH.DATE_PRESETS).some((datePreset) => datePreset === date); } +/** + * Maps the deprecated Year to date preset to This year, which the date picker lists in its place + */ +function normalizeDatePreset(date: string): string { + return date === CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE ? CONST.SEARCH.DATE_PRESETS.THIS_YEAR : date; +} + +/** + * Whether a filter value can hold a date preset. The filter form reads equal-to report field values as dates and negated ones as text. + */ +function canFilterValueBeDatePreset(filterKey: string, operator: ASTNode['operator']) { + return ( + (DATE_FILTER_KEYS as readonly string[]).includes(filterKey) || (filterKey.startsWith(CONST.SEARCH.REPORT_FIELD.DEFAULT_PREFIX) && operator === CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO) + ); +} + +/** + * Maps the deprecated Year to date preset to This year in a filter value that can hold a date preset + */ +function normalizeDatePresetFilterValue(filterKey: SyntaxFilterKey, filterValue: string | string[], operator: ASTNode['operator']): string | string[] { + if (!canFilterValueBeDatePreset(filterKey, operator)) { + return filterValue; + } + return Array.isArray(filterValue) ? filterValue.map(normalizeDatePreset) : normalizeDatePreset(filterValue); +} + +/** + * Maps the deprecated Year to date preset to This year in a query, so a search saved with it opens and saves with This year + */ +function normalizeDatePresetsInQuery(query: SearchQueryString): SearchQueryString { + const queryJSON = query ? buildSearchQueryJSON(query) : undefined; + const hasYearToDate = queryJSON?.flatFilters.some((filter) => filter.filters.some((queryFilter) => queryFilter.value === CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE)); + if (!queryJSON || !hasYearToDate) { + return query; + } + + return buildSearchQueryString(traverseAndUpdatedQuery(queryJSON, normalizeDatePresetFilterValue)); +} + /** * Returns whether a given search filter is supported in a given search data type */ @@ -1810,7 +1851,9 @@ function getDateRangeForPreset(preset: SearchDatePreset): {start: string; end: s start = startOfQuarter(lastQuarter); end = endOfQuarter(lastQuarter); break; + // Year to date is deprecated and kept as an alias of this year, so saved searches and links still resolve. case CONST.SEARCH.DATE_PRESETS.THIS_YEAR: + case CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE: start = startOfYear(now); end = endOfYear(now); break; @@ -1818,10 +1861,6 @@ function getDateRangeForPreset(preset: SearchDatePreset): {start: string; end: s start = startOfYear(lastYear); end = endOfYear(lastYear); break; - case CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE: - start = startOfYear(now); - end = now; - break; case CONST.SEARCH.DATE_PRESETS.LAST_30_DAYS: start = subDays(now, 29); end = now; @@ -2062,8 +2101,8 @@ function buildFilterFormValuesFromQuery( filtersForm[beforeKey] = getEarlierDate(beforeFilter?.value.toString(), filtersForm[beforeKey]); filtersForm[afterKey] = getLaterDate(afterFilter?.value.toString(), filtersForm[afterKey]); - filtersForm[onKey] = onFilter?.value.toString() ?? filtersForm[onKey]; - filtersForm[negatedKey] = negatedFilter?.value.toString() ?? filtersForm[negatedKey]; + filtersForm[onKey] = onFilter ? normalizeDatePreset(onFilter.value.toString()) : filtersForm[onKey]; + filtersForm[negatedKey] = negatedFilter ? normalizeDatePreset(negatedFilter.value.toString()) : filtersForm[negatedKey]; if (rangeValue) { filtersForm[rangeKey] = rangeValue; @@ -2151,7 +2190,7 @@ function buildFilterFormValuesFromQuery( filtersForm[textKey] = textFilter?.value.toString() ?? filtersForm[textKey]; filtersForm[negatedKey] = negatedFilter?.value.toString() ?? filtersForm[negatedKey]; - filtersForm[dateOnKey] = dateOnFilter?.value.toString() ?? filtersForm[dateOnKey]; + filtersForm[dateOnKey] = dateOnFilter ? normalizeDatePreset(dateOnFilter.value.toString()) : filtersForm[dateOnKey]; filtersForm[dateBeforeKey] = getEarlierDate(dateBeforeFilter?.value.toString(), filtersForm[dateBeforeKey]); filtersForm[dateAfterKey] = getLaterDate(dateAfterFilter?.value.toString(), filtersForm[dateAfterKey]); @@ -2737,7 +2776,10 @@ const sortOptionsWithEmptyValue = (a: string, b: string, localeCompare: LocaleCo /** * Given a search query, this function will standardize the query by replacing display values with their corresponding IDs. */ -function traverseAndUpdatedQuery(queryJSON: SearchQueryJSON | Readonly, computeNodeValue: (left: SyntaxFilterKey, right: string | string[]) => string | string[]) { +function traverseAndUpdatedQuery( + queryJSON: SearchQueryJSON | Readonly, + computeNodeValue: (left: SyntaxFilterKey, right: string | string[], operator: ASTNode['operator']) => string | string[], +) { const standardQuery = cloneDeep(queryJSON) as SearchQueryJSON; const filters = standardQuery.filters; const traverse = (node: ASTNode) => { @@ -2753,7 +2795,7 @@ function traverseAndUpdatedQuery(queryJSON: SearchQueryJSON | Readonly) { const queryJSON = buildSearchQueryJSON(query); @@ -2783,7 +2826,8 @@ function getQueryWithUpdatedValues(query: string, shouldSkipAmountConversion = f return; } - const computeNodeValue = (left: SyntaxFilterKey, right: string | string[]) => getUpdatedFilterValue(left, right, shouldSkipAmountConversion, policies); + const computeNodeValue = (left: SyntaxFilterKey, right: string | string[], operator: ASTNode['operator']) => + normalizeDatePresetFilterValue(left, getUpdatedFilterValue(left, right, shouldSkipAmountConversion, policies), operator); const standardizedQuery = traverseAndUpdatedQuery(queryJSON, computeNodeValue); const rawFilterList = getRawFilterListFromQuery(query); const hasInFilter = rawFilterList?.some((filter) => !filter.isDefault && filter.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN) ?? false; @@ -3270,6 +3314,8 @@ export { hasFiltersChangedFromDefault, isSearchQuerySavable, isSearchDatePreset, + normalizeDatePreset, + normalizeDatePresetsInQuery, getDateRangeForPreset, getDateFilterRange, isSearchBeforeViolationsSnapshotStarted, diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 78d39f18df8c..3754fa567ac8 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -5725,7 +5725,6 @@ function getDatePresets(filterKey: SearchDateFilterKeys, hasFeed: boolean): Sear CONST.SEARCH.DATE_PRESETS.THIS_MONTH, CONST.SEARCH.DATE_PRESETS.THIS_QUARTER, CONST.SEARCH.DATE_PRESETS.THIS_YEAR, - CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE, CONST.SEARCH.DATE_PRESETS.LAST_WEEK, CONST.SEARCH.DATE_PRESETS.LAST_MONTH, CONST.SEARCH.DATE_PRESETS.LAST_QUARTER, diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index e6e832d7ac2b..48e852793238 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -69,7 +69,7 @@ import { } from '@libs/ReportUtils'; import type {SearchKey} from '@libs/SearchKeyUtils'; import {savedSearchIDToSearchKey} from '@libs/SearchKeyUtils'; -import {buildSearchQueryJSON, buildSearchQueryString, getFooterSelectionFromQuery, serializeQueryJSONForBackend} from '@libs/SearchQueryUtils'; +import {buildSearchQueryJSON, buildSearchQueryString, getFooterSelectionFromQuery, normalizeDatePresetsInQuery, serializeQueryJSONForBackend} from '@libs/SearchQueryUtils'; import {getExpenseReportIDs, TODO_SEARCH_KEYS} from '@libs/SearchUIUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import {cancelSpan, endSpan, startSpan} from '@libs/telemetry/activeSpans'; @@ -900,8 +900,10 @@ function getOnyxLoadingData( } function saveSearch({id, queryJSON, newName}: {id: string; queryJSON: Readonly; newName?: string}) { - const saveSearchName = newName ?? queryJSON?.inputQuery ?? ''; - const jsonQuery = JSON.stringify(queryJSON); + const normalizedQuery = normalizeDatePresetsInQuery(queryJSON.inputQuery); + const savedQueryJSON = normalizedQuery === queryJSON.inputQuery ? queryJSON : (buildSearchQueryJSON(normalizedQuery) ?? queryJSON); + const saveSearchName = newName ?? savedQueryJSON?.inputQuery ?? ''; + const jsonQuery = JSON.stringify(savedQueryJSON); const optimisticData: Array> = [ { @@ -911,7 +913,7 @@ function saveSearch({id, queryJSON, newName}: {id: string; queryJSON: Readonly | undefined { const dateFilters = queryJSON.flatFilters.filter(({key}) => key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE).flatMap(({filters}) => filters); - const on = dateFilters.find(({operator}) => operator === CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO)?.value.toString(); - if (on) { + const onValue = dateFilters.find(({operator}) => operator === CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO)?.value.toString(); + if (onValue) { + const on = normalizeDatePreset(onValue); return {date: isSearchDatePreset(on) ? {preset: on} : {on}}; } diff --git a/src/pages/Insights/insightsFilters.ts b/src/pages/Insights/insightsFilters.ts index 2e2c8cb1be89..3e133dc5dbbc 100644 --- a/src/pages/Insights/insightsFilters.ts +++ b/src/pages/Insights/insightsFilters.ts @@ -29,7 +29,7 @@ type InsightsFilters = { }; const DEFAULT_INSIGHTS_FILTERS: Omit = { - date: {preset: CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE}, + date: {preset: CONST.SEARCH.DATE_PRESETS.THIS_YEAR}, policyIDs: [], groupBy: CONST.SEARCH.GROUP_BY.MONTH, }; diff --git a/src/pages/Search/SavedSearchList.tsx b/src/pages/Search/SavedSearchList.tsx index 2ed606ddd2aa..603f2dad0d29 100644 --- a/src/pages/Search/SavedSearchList.tsx +++ b/src/pages/Search/SavedSearchList.tsx @@ -19,7 +19,7 @@ import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {SearchKey} from '@libs/SearchKeyUtils'; import {savedSearchIDToSearchKey} from '@libs/SearchKeyUtils'; -import {getValidLastQuery} from '@libs/SearchQueryUtils'; +import {getValidLastQuery, normalizeDatePresetsInQuery} from '@libs/SearchQueryUtils'; import type {SavedSearchMenuItem} from '@libs/SearchUIUtils'; import {createBaseSavedSearchMenuItem, getLastSearchQuery, getOverflowMenu as getOverflowMenuUtil, SAVED_SEARCH_FALLBACK_ICON_NAME, SAVED_SEARCH_ICON_NAMES} from '@libs/SearchUIUtils'; @@ -146,7 +146,7 @@ function SavedSearchList() { const savedSearchesMenuItems = savedSearches ? Object.entries(savedSearches) .map(([key, item], index) => { - const itemQuery = getValidLastQuery(getLastSearchQuery(searchFilters, savedSearchIDToSearchKey(key)), item.query); + const itemQuery = normalizeDatePresetsInQuery(getValidLastQuery(getLastSearchQuery(searchFilters, savedSearchIDToSearchKey(key)), item.query)); return buildSavedSearchMenuItem({ item, itemQuery, diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx index 9056e6ce8e04..23f5884fe26c 100644 --- a/src/pages/Search/SearchTypeMenuNarrow.tsx +++ b/src/pages/Search/SearchTypeMenuNarrow.tsx @@ -26,7 +26,7 @@ import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils'; import {getAllTaxRates} from '@libs/PolicyUtils'; import {savedSearchIDToSearchKey} from '@libs/SearchKeyUtils'; import type {SearchKey} from '@libs/SearchKeyUtils'; -import {getValidLastQuery} from '@libs/SearchQueryUtils'; +import {getValidLastQuery, normalizeDatePresetsInQuery} from '@libs/SearchQueryUtils'; import {getItemBadgeText, getLastSearchQuery, getOverflowMenu, SAVED_SEARCH_FALLBACK_ICON_NAME, SAVED_SEARCH_ICON_NAMES, SEARCH_TYPE_MENU_ICON_NAMES} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; @@ -214,7 +214,7 @@ function SearchTypeMenuNarrow({queryJSON, onTabPress}: SearchTypeMenuNarrowProps return; } onTabPress?.(); - const query = getValidLastQuery(getLastSearchQuery(searchFilters, tabKey), searchData.query); + const query = normalizeDatePresetsInQuery(getValidLastQuery(getLastSearchQuery(searchFilters, tabKey), searchData.query)); setSearchContext(false); navigation.dispatch({ type: CONST.NAVIGATION.ACTION_TYPE.PUSH_PARAMS, diff --git a/tests/unit/Search/SearchQueryUtilsTest.ts b/tests/unit/Search/SearchQueryUtilsTest.ts index d91ebe233c62..3e293001843a 100644 --- a/tests/unit/Search/SearchQueryUtilsTest.ts +++ b/tests/unit/Search/SearchQueryUtilsTest.ts @@ -44,6 +44,7 @@ import { hasFiltersChangedFromDefault, isSearchQuerySavable, isFilterNegated, + normalizeDatePresetsInQuery, isDefaultExpenseReportsQuery, isDefaultExpensesQuery, isSearchBeforeViolationsSnapshotStarted, @@ -717,6 +718,18 @@ describe('SearchQueryUtils', () => { expect(result).toContain('footerCurrency:eur'); }); + test('maps a typed year to date preset to this year', () => { + // Given a typed query on the deprecated year to date preset + const userQuery = 'type:expense date:year-to-date'; + + // When the typed query is rebuilt before it's submitted + const result = getQueryWithUpdatedValues(userQuery); + + // Then it uses this year, so the URL matches what the date picker shows and a saved search stores + expect(result).toContain(`date:${CONST.SEARCH.DATE_PRESETS.THIS_YEAR}`); + expect(result).not.toContain(CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE); + }); + test('rebuilds a single value containing a comma as one value', () => { expect(getQueryWithUpdatedValues(String.raw`merchant:Globex\,Ltd`)).toEqual(`${defaultQuery} merchant*:"Globex,Ltd"`); expect(getQueryWithUpdatedValues('merchant:"Globex,Ltd"')).toEqual(`${defaultQuery} merchant*:"Globex,Ltd"`); @@ -2248,7 +2261,7 @@ describe('SearchQueryUtils', () => { }); test('merges date preset with inclusive range into a single range', () => { - const queryString = 'type:expense date:year-to-date date>=2026-04-01 date<=2026-04-30'; + const queryString = 'type:expense date:last-30-days date>=2026-04-01 date<=2026-04-30'; const queryJSON = buildSearchQueryJSON(queryString); if (!queryJSON) { @@ -2278,7 +2291,7 @@ describe('SearchQueryUtils', () => { }); test('merges all date-based filters, not only "date"', () => { - const queryString = 'type:expense approved:year-to-date approved>=2026-04-01 approved<=2026-04-30'; + const queryString = 'type:expense approved:last-30-days approved>=2026-04-01 approved<=2026-04-30'; const queryJSON = buildSearchQueryJSON(queryString); if (!queryJSON) { @@ -2293,7 +2306,7 @@ describe('SearchQueryUtils', () => { }); test('merges date preset and strict inequalities into a single range', () => { - const queryString = 'type:expense date:year-to-date date>2026-04-01 date<2026-04-30'; + const queryString = 'type:expense date:last-30-days date>2026-04-01 date<2026-04-30'; const queryJSON = buildSearchQueryJSON(queryString); if (!queryJSON) { @@ -2320,6 +2333,21 @@ describe('SearchQueryUtils', () => { expect(result.dateRange).toBeUndefined(); }); + test('reads the deprecated year-to-date preset as this-year', () => { + // Given a saved search from before Year to date was replaced by This year + const queryJSON = buildSearchQueryJSON('type:expense date:year-to-date approved!=year-to-date'); + if (!queryJSON) { + throw new Error('Failed to parse query string'); + } + + // When the form values are built from the query + const result = buildFilterFormValuesFromQuery(queryJSON, {}, {}, {}, {}, {}, {}); + + // Then both read as this-year, so the date picker checks the This year row it lists + expect(result.dateOn).toBe(CONST.SEARCH.DATE_PRESETS.THIS_YEAR); + expect(result.approvedNot).toBe(CONST.SEARCH.DATE_PRESETS.THIS_YEAR); + }); + test('merges multiple ranges into one', () => { const queryString = 'type:expense date>=2026-04-01 date<=2026-04-30 date>=2026-03-01 date<=2026-04-15'; const queryJSON = buildSearchQueryJSON(queryString); @@ -3490,6 +3518,109 @@ describe('SearchQueryUtils', () => { // Then the group currency doesn't count as a change expect(hasFiltersChangedFromDefault(currentQueryJSON, defaultQueryJSON, onlyGroupCurrencyIgnored)).toBe(false); }); + + it('returns false when a saved year to date search is applied again as this year', () => { + // Given a saved search on the deprecated year to date preset, which the date picker shows as this year + const defaultQueryJSON = buildSearchQueryJSON('type:expense date:year-to-date'); + + // When the unchanged date filter is applied, which writes this year back into the query + const currentQueryJSON = buildSearchQueryJSON('type:expense date:this-year'); + + if (!defaultQueryJSON || !currentQueryJSON) { + throw new Error('Failed to parse query string'); + } + + // Then the saved search isn't treated as edited, so Reset and Save stay hidden + expect(hasFiltersChangedFromDefault(currentQueryJSON, defaultQueryJSON)).toBe(false); + }); + + it('returns true when a saved year to date search is changed to another date preset', () => { + // Given a saved search on the deprecated year to date preset + const defaultQueryJSON = buildSearchQueryJSON('type:expense date:year-to-date'); + + // When a different preset is applied + const currentQueryJSON = buildSearchQueryJSON('type:expense date:last-year'); + + if (!defaultQueryJSON || !currentQueryJSON) { + throw new Error('Failed to parse query string'); + } + + // Then the change still counts, so Reset and Save appear + expect(hasFiltersChangedFromDefault(currentQueryJSON, defaultQueryJSON)).toBe(true); + }); + + it('returns false when a saved year to date report field date search is applied again as this year', () => { + // Given a saved search on a report field date using the deprecated year to date preset, which the date picker shows as this year + const defaultQueryJSON = buildSearchQueryJSON('type:expense report-field-trip-date:year-to-date'); + + // When the unchanged report field filter is applied, which writes this year back into the query + const currentQueryJSON = buildSearchQueryJSON('type:expense report-field-trip-date:this-year'); + + if (!defaultQueryJSON || !currentQueryJSON) { + throw new Error('Failed to parse query string'); + } + + // Then the saved search isn't treated as edited, so Reset and Save stay hidden + expect(hasFiltersChangedFromDefault(currentQueryJSON, defaultQueryJSON)).toBe(false); + }); + + it('returns true when a saved negated report field search on year to date is changed to this year', () => { + // Given a saved search excluding the year to date text from a report field, which the filter form keeps as text + const defaultQueryJSON = buildSearchQueryJSON('type:expense -report-field-trip-date:year-to-date'); + + // When the excluded text is changed to this year + const currentQueryJSON = buildSearchQueryJSON('type:expense -report-field-trip-date:this-year'); + + if (!defaultQueryJSON || !currentQueryJSON) { + throw new Error('Failed to parse query string'); + } + + // Then the change still counts, so Reset and Save appear + expect(hasFiltersChangedFromDefault(currentQueryJSON, defaultQueryJSON)).toBe(true); + }); + }); + + describe('normalizeDatePresetsInQuery', () => { + const getFilterValues = (query: string, filterKey: string) => + buildSearchQueryJSON(query) + ?.flatFilters.find((filter) => filter.key === filterKey) + ?.filters.map((queryFilter) => ({operator: queryFilter.operator, value: queryFilter.value})); + + it('maps year to date to this year on a date filter', () => { + // Given a search saved with the deprecated year to date preset + const query = 'type:expense date:year-to-date'; + + // When the query is normalized before it's opened or saved + const normalizedQuery = normalizeDatePresetsInQuery(query); + + // Then it uses this year, the preset the date picker lists in its place + expect(getFilterValues(normalizedQuery, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE)).toEqual([ + {operator: CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO, value: CONST.SEARCH.DATE_PRESETS.THIS_YEAR}, + ]); + }); + + it('maps year to date on a report field date but keeps a negated report field value', () => { + // Given a search with year to date on a report field date and as excluded text on another report field + const query = 'type:expense report-field-trip-date:year-to-date -report-field-notes:year-to-date'; + + // When the query is normalized + const normalizedQuery = normalizeDatePresetsInQuery(query); + + // Then only the date value changes, because the filter form keeps negated report field values as text + expect(getFilterValues(normalizedQuery, 'reportField-trip-date')).toEqual([{operator: CONST.SEARCH.SYNTAX_OPERATORS.EQUAL_TO, value: CONST.SEARCH.DATE_PRESETS.THIS_YEAR}]); + expect(getFilterValues(normalizedQuery, 'reportField-notes')).toEqual([{operator: CONST.SEARCH.SYNTAX_OPERATORS.NOT_EQUAL_TO, value: CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE}]); + }); + + it('returns the query unchanged when it has no year to date preset', () => { + // Given a search on a current date preset + const query = 'type:expense date:last-year'; + + // When the query is normalized + const normalizedQuery = normalizeDatePresetsInQuery(query); + + // Then the same query string comes back, so searches without the deprecated preset open exactly as saved + expect(normalizedQuery).toBe(query); + }); }); describe('isSearchQuerySavable', () => { diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index ffda6b4671ea..3ab76cbefe68 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -4748,6 +4748,7 @@ describe('SearchUIUtils', () => { [CONST.SEARCH.DATE_PRESETS.THIS_QUARTER, {start: '2026-01-01', end: '2026-03-31'}], [CONST.SEARCH.DATE_PRESETS.LAST_QUARTER, {start: '2025-10-01', end: '2025-12-31'}], [CONST.SEARCH.DATE_PRESETS.THIS_YEAR, {start: '2026-01-01', end: '2026-12-31'}], + [CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE, {start: '2026-01-01', end: '2026-12-31'}], [CONST.SEARCH.DATE_PRESETS.LAST_YEAR, {start: '2025-01-01', end: '2025-12-31'}], [CONST.SEARCH.DATE_PRESETS.LAST_30_DAYS, {start: '2026-01-17', end: '2026-02-15'}], [CONST.SEARCH.DATE_PRESETS.LAST_90_DAYS, {start: '2025-11-18', end: '2026-02-15'}], @@ -4758,7 +4759,8 @@ describe('SearchUIUtils', () => { // When each preset is resolved const range = getDateRangeForPreset(preset); - // Then current periods run to the end of the period, previous periods cover the whole period, and rolling day ranges end today + // Then current periods run to the end of the period, previous periods cover the whole period, rolling day ranges end today, + // and the deprecated year to date matches this year expect(range).toEqual(expectedRange); } }); @@ -16843,7 +16845,6 @@ describe('getDatePresets', () => { CONST.SEARCH.DATE_PRESETS.THIS_MONTH, CONST.SEARCH.DATE_PRESETS.THIS_QUARTER, CONST.SEARCH.DATE_PRESETS.THIS_YEAR, - CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE, CONST.SEARCH.DATE_PRESETS.LAST_WEEK, CONST.SEARCH.DATE_PRESETS.LAST_MONTH, CONST.SEARCH.DATE_PRESETS.LAST_QUARTER, diff --git a/tests/unit/SearchActionsTest.ts b/tests/unit/SearchActionsTest.ts index 42ee1a3b4b75..48a83608d05c 100644 --- a/tests/unit/SearchActionsTest.ts +++ b/tests/unit/SearchActionsTest.ts @@ -23,7 +23,7 @@ import fileDownload from '@libs/fileDownload'; import {translate} from '@libs/Localize'; import {savedSearchIDToSearchKey} from '@libs/SearchKeyUtils'; import type {SearchKey} from '@libs/SearchKeyUtils'; -import {buildSearchQueryJSON} from '@libs/SearchQueryUtils'; +import {buildSearchQueryJSON, normalizeDatePresetsInQuery} from '@libs/SearchQueryUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -150,6 +150,31 @@ describe('SearchActions', () => { }), ); }); + + it('saves a year to date query with this year', () => { + // Given a query on the deprecated year to date preset + const yearToDateQueryJSON = getQueryJSON('type:expense date:year-to-date'); + + // When it's saved + saveSearch({id: savedSearchID, queryJSON: yearToDateQueryJSON, newName: 'My search'}); + + // Then the saved query uses this year, so the saved search opens with the preset the date picker lists + const savedQueryJSON = getQueryJSON(normalizeDatePresetsInQuery(yearToDateQueryJSON.inputQuery)); + expect(savedQueryJSON.inputQuery).toContain(`date:${CONST.SEARCH.DATE_PRESETS.THIS_YEAR}`); + expect(mockWrite).toHaveBeenCalledWith( + WRITE_COMMANDS.SAVE_SEARCH, + {jsonQuery: JSON.stringify(savedQueryJSON), savedSearchID, newName: 'My search'}, + expect.objectContaining({ + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SAVED_SEARCHES, + value: {[savedSearchID]: {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, name: 'My search', query: savedQueryJSON.inputQuery}}, + }, + ], + }), + ); + }); }); describe('deleteSavedSearch', () => { diff --git a/tests/unit/hooks/useInsightsFilters.test.ts b/tests/unit/hooks/useInsightsFilters.test.ts index e8b78f2604e5..483e202bde32 100644 --- a/tests/unit/hooks/useInsightsFilters.test.ts +++ b/tests/unit/hooks/useInsightsFilters.test.ts @@ -75,9 +75,9 @@ describe('useInsightsFilters', () => { const {result} = renderHook(() => useInsightsFilters(CONST.INSIGHTS.DASHBOARD.SPEND)); await waitFor(() => expect(result.current.isResolved).toBe(true)); - // Then it reports on the year to date, across every workspace, in that currency + // Then it reports on this year, across every workspace, in that currency expect(result.current.filters).toEqual({ - date: {preset: CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE}, + date: {preset: CONST.SEARCH.DATE_PRESETS.THIS_YEAR}, policyIDs: [], groupBy: CONST.SEARCH.GROUP_BY.MONTH, groupCurrency: 'PLN', @@ -120,7 +120,7 @@ describe('useInsightsFilters', () => { // Then the defaults ignore the stored selections, keeping the default workspace currency for Group currency's Reset expect(result.current.defaultFilters).toEqual({ - date: {preset: CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE}, + date: {preset: CONST.SEARCH.DATE_PRESETS.THIS_YEAR}, policyIDs: [], groupBy: CONST.SEARCH.GROUP_BY.MONTH, groupCurrency: 'PLN', diff --git a/tests/unit/insightsCompareTest.ts b/tests/unit/insightsCompareTest.ts index d660e064d78b..46e7a5edd6ae 100644 --- a/tests/unit/insightsCompareTest.ts +++ b/tests/unit/insightsCompareTest.ts @@ -12,12 +12,14 @@ describe('resolveComparisonPeriods', () => { jest.useRealTimers(); }); - it('compares the year so far against the same stretch of the year before', () => { - // When the page reports on the year to date + it('compares year to date against the whole year before, like this year', () => { + // When the page reports on year to date, which is kept as an alias of this year const periods = resolveComparisonPeriods({preset: CONST.SEARCH.DATE_PRESETS.YEAR_TO_DATE}); - // Then each period runs from January 1st to the same day of its own year, and each day pairs with its date a year before - expect(periods).toEqual({primary: {start: '2026-01-01', end: '2026-09-22'}, comparison: {start: '2025-01-01', end: '2025-09-22'}, monthsApart: 12}); + // Then both periods are whole calendar years, a year apart + const expectedPeriods = {primary: {start: '2026-01-01', end: '2026-12-31'}, comparison: {start: '2025-01-01', end: '2025-12-31'}, monthsApart: 12}; + expect(periods).toEqual(expectedPeriods); + expect(resolveComparisonPeriods({preset: CONST.SEARCH.DATE_PRESETS.THIS_YEAR})).toEqual(expectedPeriods); }); it('compares a month against the month before it', () => { diff --git a/tests/unit/insightsFilterParsingTest.ts b/tests/unit/insightsFilterParsingTest.ts index 486dfac3510d..57fc5a85c37d 100644 --- a/tests/unit/insightsFilterParsingTest.ts +++ b/tests/unit/insightsFilterParsingTest.ts @@ -54,6 +54,17 @@ describe('insightsFilterParsing', () => { expect(day.date).toEqual({on: '2026-03-04'}); }); + it('reads the deprecated year-to-date preset as this year', () => { + // Given a dashboard saved before Year to date was replaced by This year + const query = 'groupBy:month groupCurrency:USD date:year-to-date'; + + // When its stored query is read back + const parsed = parseInsightsFilters(query); + + // Then it reads back as This year, so the date picker checks the row it lists + expect(parsed.date).toEqual({preset: CONST.SEARCH.DATE_PRESETS.THIS_YEAR}); + }); + it('reads both ends of a range, which the query carries as two separate filters', () => { // Given a query bounded at both ends const query = 'groupBy:month groupCurrency:USD date>=2026-01-01 date<=2026-03-31'; diff --git a/tests/unit/insightsGroupByUtilsTest.ts b/tests/unit/insightsGroupByUtilsTest.ts index abd246e0bfe1..c2b22cea2b04 100644 --- a/tests/unit/insightsGroupByUtilsTest.ts +++ b/tests/unit/insightsGroupByUtilsTest.ts @@ -5,7 +5,7 @@ import {getFittingGroupBy, getInsightsGroupByOptions, getQueryGroupBy} from '@pa import CONST from '@src/CONST'; const {DAY, WEEK, MONTH, QUARTER, YEAR} = CONST.SEARCH.GROUP_BY; -const {THIS_MONTH, LAST_MONTH, YEAR_TO_DATE, LAST_12_MONTHS} = CONST.SEARCH.DATE_PRESETS; +const {THIS_MONTH, LAST_MONTH, THIS_YEAR, YEAR_TO_DATE, LAST_12_MONTHS} = CONST.SEARCH.DATE_PRESETS; describe('insightsGroupByUtils', () => { beforeEach(() => { @@ -21,7 +21,7 @@ describe('insightsGroupByUtils', () => { it.each<[string, InsightsFilters['date'], Array]>([ ['this month', {preset: THIS_MONTH}, [DAY, WEEK]], ['last month', {preset: LAST_MONTH}, [DAY, WEEK]], - ['year to date in October', {preset: YEAR_TO_DATE}, [WEEK, MONTH, QUARTER]], + ['this year', {preset: THIS_YEAR}, [WEEK, MONTH, QUARTER]], ['last 12 months', {preset: LAST_12_MONTHS}, [WEEK, MONTH, QUARTER]], ['a range under two weeks', {from: '2026-03-01', to: '2026-03-10'}, [DAY]], ['a range of 13 days', {from: '2026-03-01', to: '2026-03-13'}, [DAY]], @@ -41,15 +41,15 @@ describe('insightsGroupByUtils', () => { expect(options).toEqual(expected); }); - it('offers Day and Week for year to date in January', () => { - // Given it's late January, so the year so far is a few weeks long + it('offers the whole year options for year to date in January', () => { + // Given it's late January, and year to date is a deprecated alias of this year jest.setSystemTime(new Date(2026, 0, 20)); // When the Group by options are listed for year to date const options = getInsightsGroupByOptions({preset: YEAR_TO_DATE}); - // Then the range is too short for monthly points and short enough for daily ones - expect(options).toEqual([DAY, WEEK]); + // Then they fit the whole year, not just the few weeks so far + expect(options).toEqual([WEEK, MONTH, QUARTER]); }); });