From 115325237dc6f00f97c020074a30634c8de4c15e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:41:46 +0000 Subject: [PATCH 01/17] Initial plan From 942fb1ba6bcc709690f80481086a1fbd2aad008b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:49:19 +0000 Subject: [PATCH 02/17] feat(atomic): migrate atomic-timeframe component to Lit Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../atomic-timeframe/atomic-timeframe.ts | 48 +++++++++++++++++++ .../atomic/src/components/common/index.ts | 1 + .../src/components/common/lazy-index.ts | 2 + 3 files changed, 51 insertions(+) create mode 100644 packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts new file mode 100644 index 00000000000..31d64cc2446 --- /dev/null +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts @@ -0,0 +1,48 @@ +import type {RelativeDateUnit} from '@coveo/headless'; +import {LitElement} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; +import type {Timeframe} from '@/src/components/common/facets/timeframe-facet-common'; +import {LightDomMixin} from '@/src/mixins/light-dom'; + +/** + * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. + * + * A timeframe is a span of time from now to a specific time in the past. + */ +@customElement('atomic-timeframe') +export class AtomicTimeframe + extends LightDomMixin(LitElement) + implements Timeframe +{ + /** + * The relative period of time to now. + */ + @property({type: String, reflect: true}) public period: 'past' | 'next' = + 'past'; + + /** + * The unit used to define: + * - the start date of the timeframe, if the period is `past` + * - the end date of the timeframe, if the period is `future` + */ + @property({type: String, reflect: true}) public unit!: RelativeDateUnit; + + /** + * The amount of units from which to count. + * + * For example, 10 days, 1 year, etc. + */ + @property({type: Number, reflect: true}) public amount = 1; + + /** + * The non-localized label for the timeframe. When defined, it will appear instead of the formatted value. + * Used in the `atomic-breadbox` component through the bindings store. + */ + @property({type: String, reflect: true}) public label?: string; +} + +declare global { + interface HTMLElementTagNameMap { + 'atomic-timeframe': AtomicTimeframe; + } +} diff --git a/packages/atomic/src/components/common/index.ts b/packages/atomic/src/components/common/index.ts index 6653e078a2b..b41c1a154e5 100644 --- a/packages/atomic/src/components/common/index.ts +++ b/packages/atomic/src/components/common/index.ts @@ -8,3 +8,4 @@ export {AtomicIcon} from './atomic-icon/atomic-icon.js'; export {AtomicLayoutSection} from './atomic-layout-section/atomic-layout-section.js'; export {AtomicModal} from './atomic-modal/atomic-modal.js'; export {AtomicNumericRange} from './atomic-numeric-range/atomic-numeric-range.js'; +export {AtomicTimeframe} from './atomic-timeframe/atomic-timeframe.js'; diff --git a/packages/atomic/src/components/common/lazy-index.ts b/packages/atomic/src/components/common/lazy-index.ts index 47a94102b45..8ae54ff6114 100644 --- a/packages/atomic/src/components/common/lazy-index.ts +++ b/packages/atomic/src/components/common/lazy-index.ts @@ -16,6 +16,8 @@ export default { 'atomic-modal': async () => await import('./atomic-modal/atomic-modal.js'), 'atomic-numeric-range': async () => await import('./atomic-numeric-range/atomic-numeric-range.js'), + 'atomic-timeframe': async () => + await import('./atomic-timeframe/atomic-timeframe.js'), } as Record Promise>; export type * from './index.js'; From 515802fd53b58bafa0b068bfd4d9c9a5db1776d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:53:00 +0000 Subject: [PATCH 03/17] test(atomic): add comprehensive unit tests for atomic-timeframe Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../atomic-timeframe/atomic-timeframe.spec.ts | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts new file mode 100644 index 00000000000..aa3086b8289 --- /dev/null +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts @@ -0,0 +1,144 @@ +import {html} from 'lit'; +import {describe, expect, it} from 'vitest'; +import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture'; +import type {AtomicTimeframe} from './atomic-timeframe'; +import './atomic-timeframe'; + +describe('atomic-timeframe', () => { + const renderTimeframe = async ({ + period = 'past', + unit = 'day', + amount = 1, + label, + }: { + period?: 'past' | 'next'; + unit?: string; + amount?: number; + label?: string; + } = {}) => { + const element = await renderFunctionFixture(html` + + `); + + const timeframe = element.querySelector( + 'atomic-timeframe' + ) as AtomicTimeframe; + + return {element, timeframe}; + }; + + describe('default properties', () => { + it('should have default period of "past"', async () => { + const {timeframe} = await renderTimeframe(); + expect(timeframe.period).toBe('past'); + }); + + it('should have default amount of 1', async () => { + const {timeframe} = await renderTimeframe(); + expect(timeframe.amount).toBe(1); + }); + }); + + describe('when setting period', () => { + it('should set period to "past"', async () => { + const {timeframe} = await renderTimeframe({period: 'past'}); + expect(timeframe.period).toBe('past'); + }); + + it('should set period to "next"', async () => { + const {timeframe} = await renderTimeframe({period: 'next'}); + expect(timeframe.period).toBe('next'); + }); + }); + + describe('when setting unit', () => { + it('should set unit to "day"', async () => { + const {timeframe} = await renderTimeframe({unit: 'day'}); + expect(timeframe.unit).toBe('day'); + }); + + it('should set unit to "month"', async () => { + const {timeframe} = await renderTimeframe({unit: 'month'}); + expect(timeframe.unit).toBe('month'); + }); + + it('should set unit to "year"', async () => { + const {timeframe} = await renderTimeframe({unit: 'year'}); + expect(timeframe.unit).toBe('year'); + }); + }); + + describe('when setting amount', () => { + it('should set amount to 1', async () => { + const {timeframe} = await renderTimeframe({amount: 1}); + expect(timeframe.amount).toBe(1); + }); + + it('should set amount to 10', async () => { + const {timeframe} = await renderTimeframe({amount: 10}); + expect(timeframe.amount).toBe(10); + }); + + it('should set amount to 365', async () => { + const {timeframe} = await renderTimeframe({amount: 365}); + expect(timeframe.amount).toBe(365); + }); + }); + + describe('when setting label', () => { + it('should set custom label', async () => { + const label = 'Last month'; + const {timeframe} = await renderTimeframe({label}); + expect(timeframe.label).toBe(label); + }); + + it('should set label to "Past year"', async () => { + const label = 'Past year'; + const {timeframe} = await renderTimeframe({label}); + expect(timeframe.label).toBe(label); + }); + }); + + describe('Timeframe interface implementation', () => { + it('should implement all Timeframe interface properties', async () => { + const {timeframe} = await renderTimeframe({ + period: 'past', + unit: 'month', + amount: 3, + label: 'Last quarter', + }); + + expect(timeframe).toHaveProperty('period'); + expect(timeframe).toHaveProperty('unit'); + expect(timeframe).toHaveProperty('amount'); + expect(timeframe).toHaveProperty('label'); + }); + }); + + describe('property reflection', () => { + it('should reflect period attribute', async () => { + const {timeframe} = await renderTimeframe({period: 'next'}); + expect(timeframe.getAttribute('period')).toBe('next'); + }); + + it('should reflect unit attribute', async () => { + const {timeframe} = await renderTimeframe({unit: 'week'}); + expect(timeframe.getAttribute('unit')).toBe('week'); + }); + + it('should reflect amount attribute', async () => { + const {timeframe} = await renderTimeframe({amount: 7}); + expect(timeframe.getAttribute('amount')).toBe('7'); + }); + + it('should reflect label attribute when set', async () => { + const {timeframe} = await renderTimeframe({label: 'Custom Label'}); + expect(timeframe.getAttribute('label')).toBe('Custom Label'); + }); + }); +}); From d4acc8c7f62bd59279d24319e63833a8e5de4da3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:55:44 +0000 Subject: [PATCH 04/17] docs(atomic): add MDX documentation for atomic-timeframe Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../atomic-timeframe/atomic-timeframe.mdx | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx new file mode 100644 index 00000000000..138082fefe5 --- /dev/null +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx @@ -0,0 +1,57 @@ +import { Meta } from '@storybook/addon-docs/blocks'; +import * as AtomicTimeframeStories from './atomic-timeframe.new.stories'; +import { AtomicDocTemplate } from '@/storybook-utils/documentation/atomic-doc-template'; + + + + + + +## Usage + +The `atomic-timeframe` component defines a timeframe for an `atomic-timeframe-facet` component. It must be defined within an `atomic-timeframe-facet` component to function properly. + +A timeframe represents a span of time from now to a specific time in the past (or future). + +### Basic Example + +```html + + + + + + +``` + +### With Custom Amounts + +```html + + + + + +``` + +### Future Timeframes + +```html + + + + +``` + +## Related Components + +- [atomic-timeframe-facet](../?path=/docs/atomic-timeframe-facet--default) - Search timeframe facet +- [atomic-commerce-timeframe-facet](../?path=/docs/atomic-commerce-timeframe-facet--default) - Commerce timeframe facet +- [atomic-insight-timeframe-facet](../?path=/docs/atomic-insight-timeframe-facet--default) - Insight timeframe facet + + From ad6928928d13abccfe2b28a2d3fea0d905521656 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:59:45 +0000 Subject: [PATCH 05/17] chore(atomic): update auto-generated files for atomic-timeframe Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- packages/atomic/src/components.d.ts | 67 ------------------- .../atomic/src/utils/custom-element-tags.ts | 1 + 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index 7b61b987e0f..5ddbfaba452 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -5,30 +5,24 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, RelativeDateUnit, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; import { AnyBindings } from "./components/common/interface/bindings"; import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; -import { FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "@coveo/headless/insight"; import { ItemDisplayBasicLayout, ItemDisplayDensity, ItemDisplayImageSize, ItemDisplayLayout } from "./components/common/layout/display-options"; import { ItemRenderingFunction } from "./components/common/item-list/stencil-item-list-common"; import { InsightStore } from "./components/insight/atomic-insight-interface/store"; import { Actions, InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; import { InsightResultAttachToCaseEvent } from "./components/insight/atomic-insight-result-attach-to-case-action/atomic-insight-result-attach-to-case-action"; -import { InteractiveResult as RecsInteractiveResult, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "@coveo/headless/recommendation"; import { RecsStore } from "./components/recommendations/atomic-recs-interface/store"; import { RedirectionPayload } from "./components/common/search-box/redirection-payload"; import { i18n } from "i18next"; import { SearchBoxSuggestionElement } from "./components/common/suggestions/suggestions-types"; -export { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, RelativeDateUnit, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; export { AnyBindings } from "./components/common/interface/bindings"; export { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; -export { FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "@coveo/headless/insight"; export { ItemDisplayBasicLayout, ItemDisplayDensity, ItemDisplayImageSize, ItemDisplayLayout } from "./components/common/layout/display-options"; export { ItemRenderingFunction } from "./components/common/item-list/stencil-item-list-common"; export { InsightStore } from "./components/insight/atomic-insight-interface/store"; export { Actions, InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; export { InsightResultAttachToCaseEvent } from "./components/insight/atomic-insight-result-attach-to-case-action/atomic-insight-result-attach-to-case-action"; -export { InteractiveResult as RecsInteractiveResult, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "@coveo/headless/recommendation"; export { RecsStore } from "./components/recommendations/atomic-recs-interface/store"; export { RedirectionPayload } from "./components/common/search-box/redirection-payload"; export { i18n } from "i18next"; @@ -1296,28 +1290,6 @@ export namespace Components { "setButtonVisibility": (isVisible: boolean) => Promise; "toggle": () => Promise; } - /** - * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. - * A timeframe is a span of time from now to a specific time in the past. - */ - interface AtomicTimeframe { - /** - * The amount of units from which to count. For example, 10 days, 1 year, etc. - */ - "amount": number; - /** - * The non-localized label for the timeframe. When defined, it will appear instead of the formatted value. Used in the `atomic-breadbox` component through the bindings store. - */ - "label"?: string; - /** - * The relative period of time to now. - */ - "period": 'past' | 'next'; - /** - * The unit used to define: - the start date of the timeframe, if the period is `past` - the end date of the timeframe, if the period is `future` - */ - "unit": RelativeDateUnit; - } /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-timeframe-facet` displays a facet of the results for the current query as date intervals. @@ -2208,16 +2180,6 @@ declare global { prototype: HTMLAtomicTabPopoverElement; new (): HTMLAtomicTabPopoverElement; }; - /** - * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. - * A timeframe is a span of time from now to a specific time in the past. - */ - interface HTMLAtomicTimeframeElement extends Components.AtomicTimeframe, HTMLStencilElement { - } - var HTMLAtomicTimeframeElement: { - prototype: HTMLAtomicTimeframeElement; - new (): HTMLAtomicTimeframeElement; - }; /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-timeframe-facet` displays a facet of the results for the current query as date intervals. @@ -2301,7 +2263,6 @@ declare global { "atomic-tab-bar": HTMLAtomicTabBarElement; "atomic-tab-button": HTMLAtomicTabButtonElement; "atomic-tab-popover": HTMLAtomicTabPopoverElement; - "atomic-timeframe": HTMLAtomicTimeframeElement; "atomic-timeframe-facet": HTMLAtomicTimeframeFacetElement; } } @@ -3546,28 +3507,6 @@ declare namespace LocalJSX { } interface AtomicTabPopover { } - /** - * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. - * A timeframe is a span of time from now to a specific time in the past. - */ - interface AtomicTimeframe { - /** - * The amount of units from which to count. For example, 10 days, 1 year, etc. - */ - "amount"?: number; - /** - * The non-localized label for the timeframe. When defined, it will appear instead of the formatted value. Used in the `atomic-breadbox` component through the bindings store. - */ - "label"?: string; - /** - * The relative period of time to now. - */ - "period"?: 'past' | 'next'; - /** - * The unit used to define: - the start date of the timeframe, if the period is `past` - the end date of the timeframe, if the period is `future` - */ - "unit": RelativeDateUnit; - } /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-timeframe-facet` displays a facet of the results for the current query as date intervals. @@ -3703,7 +3642,6 @@ declare namespace LocalJSX { "atomic-tab-bar": AtomicTabBar; "atomic-tab-button": AtomicTabButton; "atomic-tab-popover": AtomicTabPopover; - "atomic-timeframe": AtomicTimeframe; "atomic-timeframe-facet": AtomicTimeframeFacet; } } @@ -3926,11 +3864,6 @@ declare module "@stencil/core" { "atomic-tab-bar": LocalJSX.AtomicTabBar & JSXBase.HTMLAttributes; "atomic-tab-button": LocalJSX.AtomicTabButton & JSXBase.HTMLAttributes; "atomic-tab-popover": LocalJSX.AtomicTabPopover & JSXBase.HTMLAttributes; - /** - * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. - * A timeframe is a span of time from now to a specific time in the past. - */ - "atomic-timeframe": LocalJSX.AtomicTimeframe & JSXBase.HTMLAttributes; /** * A facet is a list of values for a certain field occurring in the results. * An `atomic-timeframe-facet` displays a facet of the results for the current query as date intervals. diff --git a/packages/atomic/src/utils/custom-element-tags.ts b/packages/atomic/src/utils/custom-element-tags.ts index 7e2018cadc0..b661a9bc80f 100644 --- a/packages/atomic/src/utils/custom-element-tags.ts +++ b/packages/atomic/src/utils/custom-element-tags.ts @@ -136,6 +136,7 @@ export const ATOMIC_CUSTOM_ELEMENT_TAGS = new Set([ 'atomic-tab-manager', 'atomic-table-element', 'atomic-text', + 'atomic-timeframe', ]); /** From 245d2ea3cdcd5dcbad364e60999e1d85be8c2780 Mon Sep 17 00:00:00 2001 From: "developer-experience-bot[bot]" <91079284+developer-experience-bot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:05:20 +0000 Subject: [PATCH 06/17] Add generated files --- .../atomic-react/src/components/commerce/components.ts | 7 +++++++ packages/atomic-react/src/components/search/components.ts | 7 +++++++ packages/atomic/src/components.d.ts | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/packages/atomic-react/src/components/commerce/components.ts b/packages/atomic-react/src/components/commerce/components.ts index 802560bc747..6bcfecff5f8 100644 --- a/packages/atomic-react/src/components/commerce/components.ts +++ b/packages/atomic-react/src/components/commerce/components.ts @@ -54,6 +54,7 @@ import { AtomicProductSectionName as LitAtomicProductSectionName, AtomicProductSectionVisual as LitAtomicProductSectionVisual, AtomicProductText as LitAtomicProductText, + AtomicTimeframe as LitAtomicTimeframe, } from '@coveo/atomic/components'; import {createComponent} from '@lit/react'; import React from 'react'; @@ -387,3 +388,9 @@ export const AtomicProductText = createComponent({ react: React, elementClass: LitAtomicProductText, }); + +export const AtomicTimeframe = createComponent({ + tagName: 'atomic-timeframe', + react: React, + elementClass: LitAtomicTimeframe, +}); diff --git a/packages/atomic-react/src/components/search/components.ts b/packages/atomic-react/src/components/search/components.ts index 6ee6b00c972..e902981c3e2 100644 --- a/packages/atomic-react/src/components/search/components.ts +++ b/packages/atomic-react/src/components/search/components.ts @@ -69,6 +69,7 @@ import { AtomicTableElement as LitAtomicTableElement, AtomicTabManager as LitAtomicTabManager, AtomicText as LitAtomicText, + AtomicTimeframe as LitAtomicTimeframe, } from '@coveo/atomic/components'; import {createComponent} from '@lit/react'; import React from 'react'; @@ -492,3 +493,9 @@ export const AtomicText = createComponent({ react: React, elementClass: LitAtomicText, }); + +export const AtomicTimeframe = createComponent({ + tagName: 'atomic-timeframe', + react: React, + elementClass: LitAtomicTimeframe, +}); diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index 5ddbfaba452..6d535c33472 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -5,24 +5,30 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; import { AnyBindings } from "./components/common/interface/bindings"; import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; +import { FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "@coveo/headless/insight"; import { ItemDisplayBasicLayout, ItemDisplayDensity, ItemDisplayImageSize, ItemDisplayLayout } from "./components/common/layout/display-options"; import { ItemRenderingFunction } from "./components/common/item-list/stencil-item-list-common"; import { InsightStore } from "./components/insight/atomic-insight-interface/store"; import { Actions, InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; import { InsightResultAttachToCaseEvent } from "./components/insight/atomic-insight-result-attach-to-case-action/atomic-insight-result-attach-to-case-action"; +import { InteractiveResult as RecsInteractiveResult, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "@coveo/headless/recommendation"; import { RecsStore } from "./components/recommendations/atomic-recs-interface/store"; import { RedirectionPayload } from "./components/common/search-box/redirection-payload"; import { i18n } from "i18next"; import { SearchBoxSuggestionElement } from "./components/common/suggestions/suggestions-types"; +export { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, FacetSortCriterion, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; export { AnyBindings } from "./components/common/interface/bindings"; export { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; +export { FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "@coveo/headless/insight"; export { ItemDisplayBasicLayout, ItemDisplayDensity, ItemDisplayImageSize, ItemDisplayLayout } from "./components/common/layout/display-options"; export { ItemRenderingFunction } from "./components/common/item-list/stencil-item-list-common"; export { InsightStore } from "./components/insight/atomic-insight-interface/store"; export { Actions, InsightResultActionClickedEvent } from "./components/insight/atomic-insight-result-action/atomic-insight-result-action"; export { InsightResultAttachToCaseEvent } from "./components/insight/atomic-insight-result-attach-to-case-action/atomic-insight-result-attach-to-case-action"; +export { InteractiveResult as RecsInteractiveResult, Result as RecsResult, ResultTemplate as RecsResultTemplate, ResultTemplateCondition as RecsResultTemplateCondition } from "@coveo/headless/recommendation"; export { RecsStore } from "./components/recommendations/atomic-recs-interface/store"; export { RedirectionPayload } from "./components/common/search-box/redirection-payload"; export { i18n } from "i18next"; From c0ddb31cd921a5285247c96e222bafefecd2fd3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:32:16 +0000 Subject: [PATCH 07/17] refactor(atomic): add validation and error handling to atomic-timeframe - Delete original Stencil atomic-timeframe.tsx file - Add LitElementWithError implementation with error state property - Add constructor with Bueno schema validation (as private static readonly) - Pass false to ValidatePropsController to only log validation errors - Flatten nested describe blocks in tests per testing conventions - Add skipped validation tests with KIT-5197 TODOs Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../atomic-timeframe/atomic-timeframe.spec.ts | 178 +++++++++--------- .../atomic-timeframe/atomic-timeframe.ts | 40 +++- .../atomic-timeframe/atomic-timeframe.tsx | 40 ---- 3 files changed, 132 insertions(+), 126 deletions(-) delete mode 100644 packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.tsx diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts index aa3086b8289..8604d38c8a6 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts @@ -32,113 +32,123 @@ describe('atomic-timeframe', () => { return {element, timeframe}; }; - describe('default properties', () => { - it('should have default period of "past"', async () => { - const {timeframe} = await renderTimeframe(); - expect(timeframe.period).toBe('past'); - }); + it('should have default period of "past"', async () => { + const {timeframe} = await renderTimeframe(); + expect(timeframe.period).toBe('past'); + }); - it('should have default amount of 1', async () => { - const {timeframe} = await renderTimeframe(); - expect(timeframe.amount).toBe(1); - }); + it('should have default amount of 1', async () => { + const {timeframe} = await renderTimeframe(); + expect(timeframe.amount).toBe(1); }); - describe('when setting period', () => { - it('should set period to "past"', async () => { - const {timeframe} = await renderTimeframe({period: 'past'}); - expect(timeframe.period).toBe('past'); - }); + it('should set period to "past"', async () => { + const {timeframe} = await renderTimeframe({period: 'past'}); + expect(timeframe.period).toBe('past'); + }); - it('should set period to "next"', async () => { - const {timeframe} = await renderTimeframe({period: 'next'}); - expect(timeframe.period).toBe('next'); - }); + it('should set period to "next"', async () => { + const {timeframe} = await renderTimeframe({period: 'next'}); + expect(timeframe.period).toBe('next'); }); - describe('when setting unit', () => { - it('should set unit to "day"', async () => { - const {timeframe} = await renderTimeframe({unit: 'day'}); - expect(timeframe.unit).toBe('day'); - }); + it('should set unit to "day"', async () => { + const {timeframe} = await renderTimeframe({unit: 'day'}); + expect(timeframe.unit).toBe('day'); + }); - it('should set unit to "month"', async () => { - const {timeframe} = await renderTimeframe({unit: 'month'}); - expect(timeframe.unit).toBe('month'); - }); + it('should set unit to "month"', async () => { + const {timeframe} = await renderTimeframe({unit: 'month'}); + expect(timeframe.unit).toBe('month'); + }); - it('should set unit to "year"', async () => { - const {timeframe} = await renderTimeframe({unit: 'year'}); - expect(timeframe.unit).toBe('year'); - }); + it('should set unit to "year"', async () => { + const {timeframe} = await renderTimeframe({unit: 'year'}); + expect(timeframe.unit).toBe('year'); }); - describe('when setting amount', () => { - it('should set amount to 1', async () => { - const {timeframe} = await renderTimeframe({amount: 1}); - expect(timeframe.amount).toBe(1); - }); + it('should set amount to 1', async () => { + const {timeframe} = await renderTimeframe({amount: 1}); + expect(timeframe.amount).toBe(1); + }); - it('should set amount to 10', async () => { - const {timeframe} = await renderTimeframe({amount: 10}); - expect(timeframe.amount).toBe(10); - }); + it('should set amount to 10', async () => { + const {timeframe} = await renderTimeframe({amount: 10}); + expect(timeframe.amount).toBe(10); + }); - it('should set amount to 365', async () => { - const {timeframe} = await renderTimeframe({amount: 365}); - expect(timeframe.amount).toBe(365); - }); + it('should set amount to 365', async () => { + const {timeframe} = await renderTimeframe({amount: 365}); + expect(timeframe.amount).toBe(365); }); - describe('when setting label', () => { - it('should set custom label', async () => { - const label = 'Last month'; - const {timeframe} = await renderTimeframe({label}); - expect(timeframe.label).toBe(label); - }); + it('should set custom label', async () => { + const label = 'Last month'; + const {timeframe} = await renderTimeframe({label}); + expect(timeframe.label).toBe(label); + }); + + it('should set label to "Past year"', async () => { + const label = 'Past year'; + const {timeframe} = await renderTimeframe({label}); + expect(timeframe.label).toBe(label); + }); - it('should set label to "Past year"', async () => { - const label = 'Past year'; - const {timeframe} = await renderTimeframe({label}); - expect(timeframe.label).toBe(label); + it('should implement all Timeframe interface properties', async () => { + const {timeframe} = await renderTimeframe({ + period: 'past', + unit: 'month', + amount: 3, + label: 'Last quarter', }); + + expect(timeframe).toHaveProperty('period'); + expect(timeframe).toHaveProperty('unit'); + expect(timeframe).toHaveProperty('amount'); + expect(timeframe).toHaveProperty('label'); }); - describe('Timeframe interface implementation', () => { - it('should implement all Timeframe interface properties', async () => { - const {timeframe} = await renderTimeframe({ - period: 'past', - unit: 'month', - amount: 3, - label: 'Last quarter', - }); + it('should reflect period attribute', async () => { + const {timeframe} = await renderTimeframe({period: 'next'}); + expect(timeframe.getAttribute('period')).toBe('next'); + }); - expect(timeframe).toHaveProperty('period'); - expect(timeframe).toHaveProperty('unit'); - expect(timeframe).toHaveProperty('amount'); - expect(timeframe).toHaveProperty('label'); - }); + it('should reflect unit attribute', async () => { + const {timeframe} = await renderTimeframe({unit: 'week'}); + expect(timeframe.getAttribute('unit')).toBe('week'); }); - describe('property reflection', () => { - it('should reflect period attribute', async () => { - const {timeframe} = await renderTimeframe({period: 'next'}); - expect(timeframe.getAttribute('period')).toBe('next'); - }); + it('should reflect amount attribute', async () => { + const {timeframe} = await renderTimeframe({amount: 7}); + expect(timeframe.getAttribute('amount')).toBe('7'); + }); - it('should reflect unit attribute', async () => { - const {timeframe} = await renderTimeframe({unit: 'week'}); - expect(timeframe.getAttribute('unit')).toBe('week'); - }); + it('should reflect label attribute when set', async () => { + const {timeframe} = await renderTimeframe({label: 'Custom Label'}); + expect(timeframe.getAttribute('label')).toBe('Custom Label'); + }); - it('should reflect amount attribute', async () => { - const {timeframe} = await renderTimeframe({amount: 7}); - expect(timeframe.getAttribute('amount')).toBe('7'); - }); + // TODO V4: KIT-5197 - Remove skip + it.skip('should set error when period is invalid', async () => { + const {timeframe} = await renderTimeframe({period: 'invalid' as 'past'}); + expect(timeframe.error).toBeInstanceOf(Error); + }); - it('should reflect label attribute when set', async () => { - const {timeframe} = await renderTimeframe({label: 'Custom Label'}); - expect(timeframe.getAttribute('label')).toBe('Custom Label'); - }); + // TODO V4: KIT-5197 - Remove skip + it.skip('should set error when unit is invalid', async () => { + const {timeframe} = await renderTimeframe({unit: 'invalid'}); + expect(timeframe.error).toBeInstanceOf(Error); + }); + + // TODO V4: KIT-5197 - Remove skip + it.skip('should set error when amount is less than 1', async () => { + const {timeframe} = await renderTimeframe({amount: 0}); + expect(timeframe.error).toBeInstanceOf(Error); + }); + + // TODO V4: KIT-5197 - Remove skip + it.skip('should set error when amount is negative', async () => { + const {timeframe} = await renderTimeframe({amount: -1}); + expect(timeframe.error).toBeInstanceOf(Error); }); }); diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts index 31d64cc2446..20a6fc9d225 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts @@ -1,7 +1,10 @@ +import {NumberValue, Schema, StringValue} from '@coveo/bueno'; import type {RelativeDateUnit} from '@coveo/headless'; import {LitElement} from 'lit'; -import {customElement, property} from 'lit/decorators.js'; +import {customElement, property, state} from 'lit/decorators.js'; import type {Timeframe} from '@/src/components/common/facets/timeframe-facet-common'; +import {ValidatePropsController} from '@/src/components/common/validate-props-controller/validate-props-controller'; +import type {LitElementWithError} from '@/src/decorators/types'; import {LightDomMixin} from '@/src/mixins/light-dom'; /** @@ -12,8 +15,28 @@ import {LightDomMixin} from '@/src/mixins/light-dom'; @customElement('atomic-timeframe') export class AtomicTimeframe extends LightDomMixin(LitElement) - implements Timeframe + implements Timeframe, LitElementWithError { + private static readonly propsSchema = new Schema({ + period: new StringValue({ + constrainTo: ['past', 'next'], + required: false, + }), + unit: new StringValue({ + constrainTo: [ + 'minute', + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ], + required: true, + emptyAllowed: false, + }), + amount: new NumberValue({min: 1, required: false}), + }); /** * The relative period of time to now. */ @@ -39,6 +62,19 @@ export class AtomicTimeframe * Used in the `atomic-breadbox` component through the bindings store. */ @property({type: String, reflect: true}) public label?: string; + + @state() public error!: Error; + + constructor() { + super(); + + new ValidatePropsController( + this, + () => ({period: this.period, unit: this.unit, amount: this.amount}), + AtomicTimeframe.propsSchema, + false + ); + } } declare global { diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.tsx b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.tsx deleted file mode 100644 index 9412860b257..00000000000 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import {RelativeDateUnit} from '@coveo/headless'; -import {Component, Prop} from '@stencil/core'; -import {Timeframe} from '../facets/stencil-timeframe-facet-common'; - - -/** - * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. - * - * A timeframe is a span of time from now to a specific time in the past. - */ -@Component({ - tag: 'atomic-timeframe', - shadow: false, -}) -export class AtomicTimeframe implements Timeframe { - /** - * The relative period of time to now. - */ - @Prop({reflect: true}) public period: 'past' | 'next' = 'past'; - - /** - * The unit used to define: - * - the start date of the timeframe, if the period is `past` - * - the end date of the timeframe, if the period is `future` - */ - @Prop({reflect: true}) public unit!: RelativeDateUnit; - - /** - * The amount of units from which to count. - * - * For example, 10 days, 1 year, etc. - */ - @Prop({reflect: true}) public amount = 1; - - /** - * The non-localized label for the timeframe. When defined, it will appear instead of the formatted value. - * Used in the `atomic-breadbox` component through the bindings store. - */ - @Prop({reflect: true}) public label?: string; -} From 0cc8621d6fe4b9abb152d99a099ac9e9c0a79421 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:07:54 +0000 Subject: [PATCH 08/17] refactor(atomic): improve atomic-timeframe tests and JSDoc - Use it.skip.each for validation tests following atomic-result-list pattern - Add validation warning tests for invalid prop values - Fix "should set period to past" test to move from "next" to "past" - Use it.each to test all valid unit values (minute, hour, day, week, month, quarter, year) - Remove redundant test for setting amount to 1 (already default) - Remove redundant "Past year" label test - Remove useless Timeframe interface test (tested implicitly) - Improve JSDoc comments for clarity and conciseness Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../atomic-timeframe/atomic-timeframe.spec.ts | 173 +++++++++++------- .../atomic-timeframe/atomic-timeframe.ts | 19 +- 2 files changed, 113 insertions(+), 79 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts index 8604d38c8a6..eff078f5223 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts @@ -1,5 +1,5 @@ import {html} from 'lit'; -import {describe, expect, it} from 'vitest'; +import {describe, expect, it, vi} from 'vitest'; import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture'; import type {AtomicTimeframe} from './atomic-timeframe'; import './atomic-timeframe'; @@ -42,8 +42,10 @@ describe('atomic-timeframe', () => { expect(timeframe.amount).toBe(1); }); - it('should set period to "past"', async () => { - const {timeframe} = await renderTimeframe({period: 'past'}); + it('should set period to "past" from "next"', async () => { + const {timeframe} = await renderTimeframe({period: 'next'}); + timeframe.period = 'past'; + await timeframe.updateComplete; expect(timeframe.period).toBe('past'); }); @@ -52,62 +54,30 @@ describe('atomic-timeframe', () => { expect(timeframe.period).toBe('next'); }); - it('should set unit to "day"', async () => { - const {timeframe} = await renderTimeframe({unit: 'day'}); - expect(timeframe.unit).toBe('day'); - }); - - it('should set unit to "month"', async () => { - const {timeframe} = await renderTimeframe({unit: 'month'}); - expect(timeframe.unit).toBe('month'); + it.each([ + 'minute', + 'hour', + 'day', + 'week', + 'month', + 'quarter', + 'year', + ])('should set unit to "%s"', async (unit) => { + const {timeframe} = await renderTimeframe({unit}); + expect(timeframe.unit).toBe(unit); }); - it('should set unit to "year"', async () => { - const {timeframe} = await renderTimeframe({unit: 'year'}); - expect(timeframe.unit).toBe('year'); - }); - - it('should set amount to 1', async () => { - const {timeframe} = await renderTimeframe({amount: 1}); - expect(timeframe.amount).toBe(1); - }); - - it('should set amount to 10', async () => { + it('should set amount to a valid value', async () => { const {timeframe} = await renderTimeframe({amount: 10}); expect(timeframe.amount).toBe(10); }); - it('should set amount to 365', async () => { - const {timeframe} = await renderTimeframe({amount: 365}); - expect(timeframe.amount).toBe(365); - }); - it('should set custom label', async () => { const label = 'Last month'; const {timeframe} = await renderTimeframe({label}); expect(timeframe.label).toBe(label); }); - it('should set label to "Past year"', async () => { - const label = 'Past year'; - const {timeframe} = await renderTimeframe({label}); - expect(timeframe.label).toBe(label); - }); - - it('should implement all Timeframe interface properties', async () => { - const {timeframe} = await renderTimeframe({ - period: 'past', - unit: 'month', - amount: 3, - label: 'Last quarter', - }); - - expect(timeframe).toHaveProperty('period'); - expect(timeframe).toHaveProperty('unit'); - expect(timeframe).toHaveProperty('amount'); - expect(timeframe).toHaveProperty('label'); - }); - it('should reflect period attribute', async () => { const {timeframe} = await renderTimeframe({period: 'next'}); expect(timeframe.getAttribute('period')).toBe('next'); @@ -128,27 +98,94 @@ describe('atomic-timeframe', () => { expect(timeframe.getAttribute('label')).toBe('Custom Label'); }); - // TODO V4: KIT-5197 - Remove skip - it.skip('should set error when period is invalid', async () => { - const {timeframe} = await renderTimeframe({period: 'invalid' as 'past'}); - expect(timeframe.error).toBeInstanceOf(Error); - }); - - // TODO V4: KIT-5197 - Remove skip - it.skip('should set error when unit is invalid', async () => { - const {timeframe} = await renderTimeframe({unit: 'invalid'}); - expect(timeframe.error).toBeInstanceOf(Error); - }); - - // TODO V4: KIT-5197 - Remove skip - it.skip('should set error when amount is less than 1', async () => { - const {timeframe} = await renderTimeframe({amount: 0}); - expect(timeframe.error).toBeInstanceOf(Error); - }); + // TODO V4: KIT-5197 - Remove this test + it.each<{ + prop: 'period' | 'unit' | 'amount'; + validValue: string | number; + invalidValue: string | number; + }>([ + { + prop: 'period', + validValue: 'past', + invalidValue: 'invalid', + }, + { + prop: 'unit', + validValue: 'day', + invalidValue: 'invalid', + }, + { + prop: 'amount', + validValue: 1, + invalidValue: 0, + }, + ])( + 'should log validation warning when #$prop is updated to invalid value', + async ({prop, validValue, invalidValue}) => { + const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => {}); + + const {timeframe} = await renderTimeframe({[prop]: validValue}); + + // biome-ignore lint/suspicious/noExplicitAny: testing invalid values + (timeframe as any)[prop] = invalidValue; + await timeframe.updateComplete; + + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'Prop validation failed for component atomic-timeframe' + ), + timeframe + ); + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining(prop), + timeframe + ); + + consoleWarnSpy.mockRestore(); + } + ); // TODO V4: KIT-5197 - Remove skip - it.skip('should set error when amount is negative', async () => { - const {timeframe} = await renderTimeframe({amount: -1}); - expect(timeframe.error).toBeInstanceOf(Error); - }); + it.skip.each<{ + prop: 'period' | 'unit' | 'amount'; + validValue: string | number; + invalidValue: string | number; + }>([ + { + prop: 'period', + validValue: 'past', + invalidValue: 'invalid', + }, + { + prop: 'unit', + validValue: 'day', + invalidValue: 'invalid', + }, + { + prop: 'amount', + validValue: 1, + invalidValue: 0, + }, + { + prop: 'amount', + validValue: 1, + invalidValue: -1, + }, + ])( + 'should set error when valid #$prop is updated to an invalid value', + async ({prop, validValue, invalidValue}) => { + const {timeframe} = await renderTimeframe({[prop]: validValue}); + + expect(timeframe.error).toBeUndefined(); + + // biome-ignore lint/suspicious/noExplicitAny: testing invalid values + (timeframe as any)[prop] = invalidValue; + await timeframe.updateComplete; + + expect(timeframe.error).toBeDefined(); + expect(timeframe.error.message).toMatch(new RegExp(prop, 'i')); + } + ); }); diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts index 20a6fc9d225..a9a4007517e 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts @@ -8,9 +8,10 @@ import type {LitElementWithError} from '@/src/decorators/types'; import {LightDomMixin} from '@/src/mixins/light-dom'; /** - * The `atomic-timeframe` component defines a timeframe of an `atomic-timeframe-facet`, and therefore must be defined within an `atomic-timeframe-facet` component. + * The `atomic-timeframe` component defines a timeframe for an `atomic-timeframe-facet`. + * This component must be a child of an `atomic-timeframe-facet` component. * - * A timeframe is a span of time from now to a specific time in the past. + * A timeframe represents a span of time relative to the current moment. */ @customElement('atomic-timeframe') export class AtomicTimeframe @@ -38,28 +39,24 @@ export class AtomicTimeframe amount: new NumberValue({min: 1, required: false}), }); /** - * The relative period of time to now. + * Specifies the direction of time relative to the current moment. */ @property({type: String, reflect: true}) public period: 'past' | 'next' = 'past'; /** - * The unit used to define: - * - the start date of the timeframe, if the period is `past` - * - the end date of the timeframe, if the period is `future` + * The unit of time for the timeframe (e.g., "day", "week", "month"). */ @property({type: String, reflect: true}) public unit!: RelativeDateUnit; /** - * The amount of units from which to count. - * - * For example, 10 days, 1 year, etc. + * The number of time units for the timeframe. */ @property({type: Number, reflect: true}) public amount = 1; /** - * The non-localized label for the timeframe. When defined, it will appear instead of the formatted value. - * Used in the `atomic-breadbox` component through the bindings store. + * A custom, non-localized label for the timeframe. + * When specified, this label appears instead of the auto-generated timeframe description. */ @property({type: String, reflect: true}) public label?: string; From 5f31800d366217e9234b84b336a870e02d289259 Mon Sep 17 00:00:00 2001 From: "developer-experience-bot[bot]" <91079284+developer-experience-bot[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:36:42 +0000 Subject: [PATCH 09/17] Add generated files --- packages/atomic/src/components/common/lazy-index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atomic/src/components/common/lazy-index.ts b/packages/atomic/src/components/common/lazy-index.ts index 076c7b8b70d..f833bc8bb21 100644 --- a/packages/atomic/src/components/common/lazy-index.ts +++ b/packages/atomic/src/components/common/lazy-index.ts @@ -16,10 +16,10 @@ export default { 'atomic-modal': async () => await import('./atomic-modal/atomic-modal.js'), 'atomic-numeric-range': async () => await import('./atomic-numeric-range/atomic-numeric-range.js'), - 'atomic-timeframe': async () => - await import('./atomic-timeframe/atomic-timeframe.js'), 'atomic-tab-bar': async () => await import('./atomic-tab-bar/atomic-tab-bar.js'), + 'atomic-timeframe': async () => + await import('./atomic-timeframe/atomic-timeframe.js'), } as Record Promise>; export type * from './index.js'; From 070e91497410c207ed3c8b418894ab792ef9c516 Mon Sep 17 00:00:00 2001 From: fbeaudoincoveo Date: Tue, 16 Dec 2025 15:37:48 -0500 Subject: [PATCH 10/17] reorder atomic-timeframe import in lazy-index --- packages/atomic/src/components/common/lazy-index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atomic/src/components/common/lazy-index.ts b/packages/atomic/src/components/common/lazy-index.ts index 076c7b8b70d..f833bc8bb21 100644 --- a/packages/atomic/src/components/common/lazy-index.ts +++ b/packages/atomic/src/components/common/lazy-index.ts @@ -16,10 +16,10 @@ export default { 'atomic-modal': async () => await import('./atomic-modal/atomic-modal.js'), 'atomic-numeric-range': async () => await import('./atomic-numeric-range/atomic-numeric-range.js'), - 'atomic-timeframe': async () => - await import('./atomic-timeframe/atomic-timeframe.js'), 'atomic-tab-bar': async () => await import('./atomic-tab-bar/atomic-tab-bar.js'), + 'atomic-timeframe': async () => + await import('./atomic-timeframe/atomic-timeframe.js'), } as Record Promise>; export type * from './index.js'; From 5df4a3281d68f7a32817b840df515775b8835a0f Mon Sep 17 00:00:00 2001 From: fbeaudoincoveo Date: Tue, 16 Dec 2025 16:04:36 -0500 Subject: [PATCH 11/17] Improve typing --- .../common/atomic-timeframe/atomic-timeframe.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts index eff078f5223..33c7027983c 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts @@ -3,6 +3,7 @@ import {describe, expect, it, vi} from 'vitest'; import {renderFunctionFixture} from '@/vitest-utils/testing-helpers/fixture'; import type {AtomicTimeframe} from './atomic-timeframe'; import './atomic-timeframe'; +import type {RelativeDateUnit} from '@coveo/headless'; describe('atomic-timeframe', () => { const renderTimeframe = async ({ @@ -12,7 +13,7 @@ describe('atomic-timeframe', () => { label, }: { period?: 'past' | 'next'; - unit?: string; + unit?: RelativeDateUnit; amount?: number; label?: string; } = {}) => { @@ -54,7 +55,7 @@ describe('atomic-timeframe', () => { expect(timeframe.period).toBe('next'); }); - it.each([ + it.each([ 'minute', 'hour', 'day', From ee47f5eabb9018efffe535853a29ac8f3ce0d88d Mon Sep 17 00:00:00 2001 From: fbeaudoincoveo Date: Tue, 16 Dec 2025 16:04:46 -0500 Subject: [PATCH 12/17] Update comments in atomic-timeframe component for clarity --- .../components/common/atomic-timeframe/atomic-timeframe.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts index a9a4007517e..fdabb784b30 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.ts @@ -11,7 +11,7 @@ import {LightDomMixin} from '@/src/mixins/light-dom'; * The `atomic-timeframe` component defines a timeframe for an `atomic-timeframe-facet`. * This component must be a child of an `atomic-timeframe-facet` component. * - * A timeframe represents a span of time relative to the current moment. + * A timeframe represents a span of time relative to the current moment, either in the past or future. */ @customElement('atomic-timeframe') export class AtomicTimeframe @@ -39,7 +39,7 @@ export class AtomicTimeframe amount: new NumberValue({min: 1, required: false}), }); /** - * Specifies the direction of time relative to the current moment. + * The direction of time relative to the current moment. */ @property({type: String, reflect: true}) public period: 'past' | 'next' = 'past'; From 6b41383fcf8a6ab70cdbc414ac6f37df6b1e7087 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:09:14 +0000 Subject: [PATCH 13/17] refactor(atomic): add MSW API mocking to atomic-timeframe stories - Import MockSearchApi and create searchApiHarness instance - Add MSW handlers to story parameters for API mocking - Move decorator with atomic-timeframe-facet to meta-level - Import html from lit/static-html.js for consistency - Add additional story variants: WithPastPeriod, WithNextPeriod, MultipleTimeframes - Follow pattern from atomic-sort-expression.new.stories.tsx Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../atomic-timeframe.new.stories.tsx | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx index a791c41c3f5..198ef37d50e 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx @@ -1,9 +1,11 @@ import type {Meta, StoryObj as Story} from '@storybook/web-components-vite'; import {getStorybookHelpers} from '@wc-toolkit/storybook-helpers'; -import {html} from 'lit'; +import {html} from 'lit/static-html.js'; +import {MockSearchApi} from '@/storybook-utils/api/search/mock'; import {parameters} from '@/storybook-utils/common/common-meta-parameters'; import {wrapInSearchInterface} from '@/storybook-utils/search/search-interface-wrapper'; +const searchApiHarness = new MockSearchApi(); const {decorator, play} = wrapInSearchInterface(); const {events, args, argTypes, template} = getStorybookHelpers( 'atomic-timeframe', @@ -16,9 +18,17 @@ const meta: Meta = { id: 'atomic-timeframe', render: (args) => template(args), - decorators: [decorator], + decorators: [ + (story) => html` + ${story()} + `, + decorator, + ], parameters: { ...parameters, + msw: { + handlers: [...searchApiHarness.handlers], + }, actions: { handles: events, }, @@ -34,9 +44,36 @@ export default meta; export const Default: Story = { name: 'atomic-timeframe', args: {unit: 'year'}, - decorators: [ - (story) => html` - ${story()} - `, - ], +}; + +export const WithPastPeriod: Story = { + name: 'Past Timeframe', + args: { + period: 'past', + unit: 'month', + amount: 3, + label: 'Last 3 Months', + }, +}; + +export const WithNextPeriod: Story = { + name: 'Next Timeframe', + args: { + period: 'next', + unit: 'week', + amount: 2, + label: 'Next 2 Weeks', + }, +}; + +export const MultipleTimeframes: Story = { + name: 'Multiple Timeframes', + render: () => html` + + + + + + + `, }; From 82eeb11c742780f24d8cdf2ba8f2d5a2fbe85e29 Mon Sep 17 00:00:00 2001 From: fbeaudoincoveo Date: Wed, 17 Dec 2025 11:48:20 -0500 Subject: [PATCH 14/17] Fix stories --- .../atomic-timeframe.new.stories.tsx | 215 +++++++++++++++--- 1 file changed, 187 insertions(+), 28 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx index 198ef37d50e..78debb0601c 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.new.stories.tsx @@ -1,12 +1,14 @@ import type {Meta, StoryObj as Story} from '@storybook/web-components-vite'; import {getStorybookHelpers} from '@wc-toolkit/storybook-helpers'; -import {html} from 'lit/static-html.js'; +import {html} from 'lit'; import {MockSearchApi} from '@/storybook-utils/api/search/mock'; import {parameters} from '@/storybook-utils/common/common-meta-parameters'; import {wrapInSearchInterface} from '@/storybook-utils/search/search-interface-wrapper'; const searchApiHarness = new MockSearchApi(); -const {decorator, play} = wrapInSearchInterface(); +const {decorator, play} = wrapInSearchInterface({ + includeCodeRoot: false, +}); const {events, args, argTypes, template} = getStorybookHelpers( 'atomic-timeframe', {excludeCategories: ['methods']} @@ -17,13 +19,12 @@ const meta: Meta = { title: 'Common/Timeframe', id: 'atomic-timeframe', - render: (args) => template(args), - decorators: [ - (story) => html` - ${story()} - `, - decorator, - ], + render: (args) => html` + + ${template(args)} + + `, + decorators: [decorator], parameters: { ...parameters, msw: { @@ -33,17 +34,71 @@ const meta: Meta = { handles: events, }, }, - args, - argTypes, - + args: { + ...args, + unit: 'week', + amount: 1, + period: 'past', + }, + argTypes: { + ...argTypes, + unit: { + control: {type: 'select'}, + options: ['minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'], + }, + amount: { + control: {type: 'number', min: 1}, + }, + }, + beforeEach: async () => { + searchApiHarness.searchEndpoint.clear(); + }, play, }; export default meta; export const Default: Story = { - name: 'atomic-timeframe', - args: {unit: 'year'}, + args: { + period: 'past', + unit: 'week', + amount: 1, + }, + beforeEach: async () => { + const now = Date.now(); + + searchApiHarness.searchEndpoint.mockOnce((response) => { + if (!('results' in response)) return response; + return { + ...response, + // biome-ignore lint/suspicious/noExplicitAny: Mock response type needs flexibility + results: response.results.slice(0, 10).map((r: any, i: number) => ({ + ...r, + raw: { + ...r.raw, + date: now - i * 24 * 60 * 60 * 1000, // Past dates: last 10 days + }, + })), + facets: [ + ...response.facets, + { + facetId: 'date', + field: 'date', + moreValuesAvailable: false, + values: [ + { + start: 'past-1-week', + end: 'now', + endInclusive: false, + state: 'idle', + numberOfResults: 7, + }, + ], + }, + ], + }; + }); + }, }; export const WithPastPeriod: Story = { @@ -52,7 +107,42 @@ export const WithPastPeriod: Story = { period: 'past', unit: 'month', amount: 3, - label: 'Last 3 Months', + }, + beforeEach: async () => { + const now = Date.now(); + const oneMonth = 30 * 24 * 60 * 60 * 1000; + + searchApiHarness.searchEndpoint.mockOnce((response) => { + if (!('results' in response)) return response; + return { + ...response, + // biome-ignore lint/suspicious/noExplicitAny: Mock response type needs flexibility + results: response.results.slice(0, 10).map((r: any, i: number) => ({ + ...r, + raw: { + ...r.raw, + date: now - i * oneMonth * 0.3, // Spread over last 3 months + }, + })), + facets: [ + ...response.facets, + { + facetId: 'date', + field: 'date', + moreValuesAvailable: false, + values: [ + { + start: 'past-3-month', + end: 'now', + endInclusive: false, + state: 'idle', + numberOfResults: 42, + }, + ], + }, + ], + }; + }); }, }; @@ -60,20 +150,89 @@ export const WithNextPeriod: Story = { name: 'Next Timeframe', args: { period: 'next', - unit: 'week', - amount: 2, - label: 'Next 2 Weeks', + unit: 'year', + amount: 1, + }, + beforeEach: async () => { + const now = Date.now(); + const oneYear = 365 * 24 * 60 * 60 * 1000; + + searchApiHarness.searchEndpoint.mockOnce((response) => { + if (!('results' in response)) return response; + return { + ...response, + // biome-ignore lint/suspicious/noExplicitAny: Mock response type needs flexibility + results: response.results.slice(0, 10).map((r: any, i: number) => ({ + ...r, + raw: { + ...r.raw, + date: now + (i + 1) * oneYear, // Future dates: 1-10 years from now + }, + })), + facets: [ + ...response.facets, + { + facetId: 'date', + field: 'date', + moreValuesAvailable: false, + values: [ + { + start: 'now', + end: 'next-1-year', + endInclusive: false, + state: 'idle', + numberOfResults: 5, + }, + ], + }, + ], + }; + }); }, }; -export const MultipleTimeframes: Story = { - name: 'Multiple Timeframes', - render: () => html` - - - - - - - `, +export const WithCustomLabel: Story = { + name: 'With Custom Label', + args: { + period: 'past', + unit: 'month', + amount: 6, + label: 'Last Semester', + }, + beforeEach: async () => { + const now = Date.now(); + const oneMonth = 30 * 24 * 60 * 60 * 1000; + + searchApiHarness.searchEndpoint.mockOnce((response) => { + if (!('results' in response)) return response; + return { + ...response, + // biome-ignore lint/suspicious/noExplicitAny: Mock response type needs flexibility + results: response.results.slice(0, 10).map((r: any, i: number) => ({ + ...r, + raw: { + ...r.raw, + date: now - i * oneMonth * 0.6, // Spread over last 6 months + }, + })), + facets: [ + ...response.facets, + { + facetId: 'date', + field: 'date', + moreValuesAvailable: false, + values: [ + { + start: 'past-6-month', + end: 'now', + endInclusive: false, + state: 'idle', + numberOfResults: 28, + }, + ], + }, + ], + }; + }); + }, }; From 92a9d2a0d21a4b92771d14edabcd61061003dc39 Mon Sep 17 00:00:00 2001 From: fbeaudoincoveo Date: Wed, 17 Dec 2025 11:49:01 -0500 Subject: [PATCH 15/17] Update docs --- .../atomic-timeframe/atomic-timeframe.mdx | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx index 138082fefe5..58bfd0900c1 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.mdx @@ -12,46 +12,28 @@ import { AtomicDocTemplate } from '@/storybook-utils/documentation/atomic-doc-te className="AtomicTimeframe" > -## Usage +The `atomic-timeframe` component defines a relative date range for filtering results in a timeframe facet. It allows users to filter by periods like "past week", "next month", or other custom time ranges. -The `atomic-timeframe` component defines a timeframe for an `atomic-timeframe-facet` component. It must be defined within an `atomic-timeframe-facet` component to function properly. - -A timeframe represents a span of time from now to a specific time in the past (or future). - -### Basic Example - -```html - - - - - - -``` - -### With Custom Amounts - -```html - - - - - -``` - -### Future Timeframes +This component must be placed as a child of `atomic-timeframe-facet` (for search interfaces) or `atomic-insight-timeframe-facet` (for insight panels). ```html - - - - + + ... + + ... + + ... + + + + + + ``` ## Related Components - [atomic-timeframe-facet](../?path=/docs/atomic-timeframe-facet--default) - Search timeframe facet -- [atomic-commerce-timeframe-facet](../?path=/docs/atomic-commerce-timeframe-facet--default) - Commerce timeframe facet - [atomic-insight-timeframe-facet](../?path=/docs/atomic-insight-timeframe-facet--default) - Insight timeframe facet From 8c80d46fec9d711a846b9d22f79cd7ca3dcb2839 Mon Sep 17 00:00:00 2001 From: fbeaudoincoveo Date: Wed, 17 Dec 2025 12:15:14 -0500 Subject: [PATCH 16/17] Commit generated files --- packages/atomic/src/components.d.ts | 6 ++---- packages/atomic/src/components/common/index.ts | 1 + packages/atomic/src/components/common/lazy-index.ts | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/atomic/src/components.d.ts b/packages/atomic/src/components.d.ts index 8d0810780fc..e5cc3f9f364 100644 --- a/packages/atomic/src/components.d.ts +++ b/packages/atomic/src/components.d.ts @@ -5,7 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, RelativeDateUnit, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; +import { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; import { AnyBindings } from "./components/common/interface/bindings"; import { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; import { FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "@coveo/headless/insight"; @@ -19,7 +19,7 @@ import { RecsStore } from "./components/recommendations/atomic-recs-interface/st import { RedirectionPayload } from "./components/common/search-box/redirection-payload"; import { i18n } from "i18next"; import { SearchBoxSuggestionElement } from "./components/common/suggestions/suggestions-types"; -export { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, RelativeDateUnit, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; +export { DateFilterRange, DateRangeRequest, FacetResultsMustMatch, GeneratedAnswer, GeneratedAnswerCitation, InlineLink, InteractiveCitation, NumericFilter, NumericFilterState, RangeFacetRangeAlgorithm, RangeFacetSortCriterion, Result, ResultTemplate, ResultTemplateCondition } from "@coveo/headless"; export { AnyBindings } from "./components/common/interface/bindings"; export { NumberInputType } from "./components/common/facets/facet-number-input/number-input-type"; export { FacetSortCriterion as InsightFacetSortCriterion, FoldedResult as InsightFoldedResult, InteractiveResult as InsightInteractiveResult, RangeFacetRangeAlgorithm as InsightRangeFacetRangeAlgorithm, RangeFacetSortCriterion as InsightRangeFacetSortCriterion, Result as InsightResult, ResultTemplate as InsightResultTemplate, ResultTemplateCondition as InsightResultTemplateCondition, UserAction as IUserAction } from "@coveo/headless/insight"; @@ -2008,7 +2008,6 @@ declare global { "atomic-stencil-facet-date-input": HTMLAtomicStencilFacetDateInputElement; "atomic-suggestion-renderer": HTMLAtomicSuggestionRendererElement; "atomic-tab-button": HTMLAtomicTabButtonElement; - "atomic-timeframe": HTMLAtomicTimeframeElement; "atomic-timeframe-facet": HTMLAtomicTimeframeFacetElement; } } @@ -3219,7 +3218,6 @@ declare namespace LocalJSX { "atomic-stencil-facet-date-input": AtomicStencilFacetDateInput; "atomic-suggestion-renderer": AtomicSuggestionRenderer; "atomic-tab-button": AtomicTabButton; - "atomic-timeframe": AtomicTimeframe; "atomic-timeframe-facet": AtomicTimeframeFacet; } } diff --git a/packages/atomic/src/components/common/index.ts b/packages/atomic/src/components/common/index.ts index c8c7776da25..2d4a453cd27 100644 --- a/packages/atomic/src/components/common/index.ts +++ b/packages/atomic/src/components/common/index.ts @@ -10,3 +10,4 @@ export {AtomicModal} from './atomic-modal/atomic-modal.js'; export {AtomicNumericRange} from './atomic-numeric-range/atomic-numeric-range.js'; export {AtomicTabBar} from './atomic-tab-bar/atomic-tab-bar.js'; export {AtomicTabPopover} from './atomic-tab-popover/atomic-tab-popover.js'; +export {AtomicTimeframe} from './atomic-timeframe/atomic-timeframe.js'; diff --git a/packages/atomic/src/components/common/lazy-index.ts b/packages/atomic/src/components/common/lazy-index.ts index c3be65d3740..43369f06052 100644 --- a/packages/atomic/src/components/common/lazy-index.ts +++ b/packages/atomic/src/components/common/lazy-index.ts @@ -20,6 +20,8 @@ export default { await import('./atomic-tab-bar/atomic-tab-bar.js'), 'atomic-tab-popover': async () => await import('./atomic-tab-popover/atomic-tab-popover.js'), + 'atomic-timeframe': async () => + await import('./atomic-timeframe/atomic-timeframe.js'), } as Record Promise>; export type * from './index.js'; From d36076a425d620462ef148cb35812008f88cf422 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:36:48 +0000 Subject: [PATCH 17/17] refactor(atomic): simplify period test to create element with desired value Apply bot suggestion to create element with period='past' directly rather than modifying post-render, following the established pattern of creating elements with desired props. Co-authored-by: fbeaudoincoveo <23503066+fbeaudoincoveo@users.noreply.github.com> --- .../common/atomic-timeframe/atomic-timeframe.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts index 33c7027983c..5e6a217e8de 100644 --- a/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts +++ b/packages/atomic/src/components/common/atomic-timeframe/atomic-timeframe.spec.ts @@ -43,10 +43,8 @@ describe('atomic-timeframe', () => { expect(timeframe.amount).toBe(1); }); - it('should set period to "past" from "next"', async () => { - const {timeframe} = await renderTimeframe({period: 'next'}); - timeframe.period = 'past'; - await timeframe.updateComplete; + it('should set period to "past"', async () => { + const {timeframe} = await renderTimeframe({period: 'past'}); expect(timeframe.period).toBe('past'); });