diff --git a/.cspell.json b/.cspell.json index c4182dd652f..970574591c7 100644 --- a/.cspell.json +++ b/.cspell.json @@ -9,6 +9,7 @@ "words": [ "adjustednumberoflikes", "allo", + "angular", "Animalia", "ARROWUP", "Artemia", @@ -18,6 +19,7 @@ "azerty", "bazz", "behaviour", + "biomejs", "bloup", "bpsb", "CAPI", @@ -60,6 +62,7 @@ "gmailmessage", "gzipped", "headless", + "IDREF", "IIFE", "inat", "initializable", @@ -83,6 +86,7 @@ "messageid", "metadatas", "Modernizr", + "myapp", "mycoveocloudorganizationg", "mycoveoorganization", "mycoveoorganizationg", @@ -99,6 +103,7 @@ "mytoken", "Nautica", "Neogastropoda", + "nextjs", "nocheck", "nofield", "nononono", @@ -117,6 +122,7 @@ "ontryitnow", "organisation", "oups", + "oxlint", "pcss", "permanentid", "pino", @@ -125,6 +131,7 @@ "poutine", "predev", "Pyrenomycetes", + "react", "reduxjs", "Résultats", "rowid", @@ -266,6 +273,7 @@ "spworkflowprocess", "spworkflowprocesslist", "spworkspacepagelist", + "stencil", "stenciljs", "SVCC", "TDIDF", @@ -274,13 +282,17 @@ "thisisanotherurl", "thisisaurl", "thisisnotadate", + "tolocalestring", + "trivago", "tryitnow", "tsdoc", "ttfb", + "Turborepo", "uikit", "unexclude", "unexcluded", "univer", + "unmigrated", "Unregisters", "unstub", "unsub", @@ -289,24 +301,13 @@ "upvoted", "visi", "visio", + "vuejs", + "WCAG", "ytlikecount", "ytvideoduration", "ytvideoid", "ytviewcount", - "trivago", - "biomejs", - "oxlint", - "tolocalestring", "γίγγλυμος", - "στόμα", - "myapp", - "Turborepo", - "react", - "angular", - "vuejs", - "nextjs", - "stencil", - "WCAG", - "IDREF" + "στόμα" ] } diff --git a/.github/prompts/generate-vitest-tests-atomic-lit-components.prompt.md b/.github/prompts/generate-vitest-tests-atomic-lit-components.prompt.md index 0a09da35322..e5b7ee01066 100644 --- a/.github/prompts/generate-vitest-tests-atomic-lit-components.prompt.md +++ b/.github/prompts/generate-vitest-tests-atomic-lit-components.prompt.md @@ -15,64 +15,68 @@ Read `../instructions/tests-atomic.instructions.md` before starting. **Component structure knowledge:** - Molecules - Integrate with Headless controllers (search-box, facets, pager) +- Result/Product templates - Get data from parent context (`atomic-result`, `atomic-product`) - Atoms - Basic UI elements (buttons, icons) - Enzymes - Complex business logic components -**Determine testability:** If component isn't a molecule or doesn't integrate with Headless, this prompt may not apply. +## Result Template Component Detection -## Find Similar Tested Components +**Result template components** (e.g., `atomic-result-*`, `atomic-field-*`, commerce `atomic-product-*`) don't integrate with Headless controllers directly. They: +- Get data from parent context via `createResultContextController` or `createItemContextController` +- May use `ResultTemplatesHelpers.getResultProperty` for field access +- Live in `result-template-components/` or `product-template-components/` directories -**First, find equivalent components in other use cases (search/commerce/insight/recommendations) with existing Lit tests.** +**Fixtures available:** +- Search: `renderInAtomicResult` from `atomic-result-fixture.ts` +- Commerce: `renderInAtomicProduct` from `atomic-product-fixture.ts` -**Example:** Testing `atomic-query-error` (search) → look for `atomic-commerce-query-error/*.spec.ts` (commerce) +**Example usage:** See `atomic-product-price.spec.ts` (commerce) or `atomic-result-number.spec.ts` (search) -**How to identify:** -- Extract base name: `atomic-[use-case-]component-name` → `component-name` -- Search other use case folders for `atomic-*{base-name}*.spec.ts` -- Verify Lit tests: imports from `.ts` (not `.tsx`), uses `renderInAtomic*Interface` helpers - -**Analyze similar test file for:** -- `renderComponent` helper structure -- Locator patterns (`page.getByRole`, shadow parts) -- Console mocking strategy (nested describe blocks for errors) -- Controller mocking approach -- Test organization and assertion patterns - -**If no similar tests found:** Ask user if they know of one to reference. - -**Include pattern discovery as a todo item:** -- [ ] Find and analyze similar tested component in other use cases +**Continue with this prompt if component is a molecule, enzyme, or result/product template component.** ## Analysis Phase Before writing tests: -1. **Find and analyze similar tested component in other use cases** (see "Find Similar Tested Components" section above) -2. **Verify test fixtures exist** - Check `vitest-utils/testing-helpers/fixtures/headless/{interface}/` for `buildFake{ControllerName}`. If missing, create following pattern from similar fixture (e.g., `summary-controller.ts`): - ```typescript - export const defaultState = {...} satisfies ControllerState; - export const defaultImplementation = { - subscribe: genericSubscribe, - state: defaultState, - } satisfies Controller; - export const buildFakeController = ({implementation, state} = {}) => ({ - ...defaultImplementation, - ...implementation, - ...(state && {state: {...defaultState, ...state}}), - }); - ``` +1. **Identify component category and find reference tests:** + - **Molecules/enzymes:** Find equivalent in other use cases (search/commerce/insight/recommendations) + - Extract base name: `atomic-[use-case-]component-name` → `component-name` + - Search: `atomic-*{base-name}*.spec.ts` in other use case folders + - Verify Lit tests: imports from `.ts` (not `.tsx`), uses `renderInAtomic*Interface` + - **Result/product templates:** Find similar template component tests + - Check `atomic-product-price.spec.ts`, `atomic-result-number.spec.ts` + - **Analyze reference test for:** render helper structure, locator patterns, mocking strategy, test organization + - **If no similar tests found:** Ask user if they know of one to reference +2. **Verify test fixtures exist:** + - **Molecules/enzymes:** Check `vitest-utils/testing-helpers/fixtures/headless/{interface}/` for `buildFake{ControllerName}`. If missing, create following pattern from similar fixture (e.g., `summary-controller.ts`): + ```typescript + export const defaultState = {...} satisfies ControllerState; + export const defaultImplementation = { + subscribe: genericSubscribe, + state: defaultState, + } satisfies Controller; + export const buildFakeController = ({implementation, state} = {}) => ({ + ...defaultImplementation, + ...implementation, + ...(state && {state: {...defaultState, ...state}}), + }); + ``` + - **Result/product templates:** Ensure `atomic-result-fixture.ts` or `atomic-product-fixture.ts` exists (already available) 3. Read component implementation 4. Identify public API: properties, methods, events -5. **Verify component type** - Stop if not a molecule/enzyme integrating with Headless -6. Note Headless controller dependencies -7. Check for i18n or custom styling requirements +5. Note dependencies: + - **Molecules/enzymes:** Headless controller dependencies + - **Result/product templates:** Result/product fields accessed, custom events +6. Check for i18n or custom styling requirements 8. **Identify console logging scenarios:** - `@bindStateToController` decorator (logs errors on missing initialize/controller) - Parent element requirements (logs errors when rendered outside required parent) - Prop validation with console warnings - Deprecated prop usage -## Test File Pattern +## Test File Patterns + +### Molecule/Enzyme Pattern Reference: `packages/atomic/scripts/generate-component-templates/component.spec.ts.hbs` @@ -156,11 +160,68 @@ describe('atomic-component', () => { }); ``` +### Result/Product Template Pattern + +For result/product template components, use context fixtures: + +**✅ DO:** +```typescript +import {type Result, ResultTemplatesHelpers} from '@coveo/headless'; +import {html} from 'lit'; +import {ifDefined} from 'lit/directives/if-defined.js'; +import {describe, expect, it, vi} from 'vitest'; +import {renderInAtomicResult} from '@/vitest-utils/testing-helpers/fixtures/atomic/search/atomic-result-fixture'; +import {buildFakeSearchEngine} from '@/vitest-utils/testing-helpers/fixtures/headless/search/engine'; +import type {AtomicResultNumber} from './atomic-result-number'; +import './atomic-result-number'; + +vi.mock('@coveo/headless', async () => { + const actual = await vi.importActual('@coveo/headless'); + return { + ...actual, + ResultTemplatesHelpers: { + getResultProperty: vi.fn(), + }, + }; +}); + +describe('atomic-result-number', () => { + const mockedEngine = buildFakeSearchEngine(); + const mockResult: Partial = { + raw: {size: 1024} as Result['raw'], + }; + + const renderComponent = async ({ + props = {}, + result = mockResult, + }: { + props?: Partial<{field: string}>; + result?: Partial; + } = {}) => { + const {element} = await renderInAtomicResult({ + template: html``, + selector: 'atomic-result-number', + result: result as Result, + bindings: (bindings) => { + bindings.engine = mockedEngine; + return bindings; + }, + }); + return {element}; + }; + + // Tests... +}); +``` + +**Note:** Commerce components use `renderInAtomicProduct` with `product` prop instead of `result`. + **Key patterns from actual codebase:** - Return `{element, locators, parts}` from render function for clean test access - Use `page.getByRole()` for semantic locators - Define `parts()` function for shadow DOM queries -- Mock headless with `vi.mock('@coveo/headless', {spy: true})` +- Mock headless controllers with `vi.mock('@coveo/headless', {spy: true})` +- Mock `ResultTemplatesHelpers` with partial import for result/product templates - Use `ifDefined()` for optional props/content ## Critical Patterns @@ -198,6 +259,11 @@ it('should render button when enabled', async () => { const {parts} = await renderComponent({enabled: true}); await expect.element(parts.button).toBeInTheDocument(); }); + +// Test components that self-remove on error/null +const {atomicResult} = await renderComponent({props: {field: 'nonexistent'}}); +const element = atomicResult.querySelector('atomic-result-number'); +expect(element).toBeNull(); // Component removed itself ``` **❌ DON'T:** @@ -219,26 +285,35 @@ it('should render all parts', async () => { // When some parts are conditional, split into separate tests }); -// Don't manually create controller mocks +// Don't manually create controller mocks - use buildFake* helpers const mockController = {state: {}, subscribe: vi.fn()}; + +// Don't test error properties on components that self-remove +const {element} = await renderComponent({/* triggers removal */}); +expect(element.error).toBe(...); // Element is null, can't access properties ``` ## Test Coverage Checklist **Pattern Analysis:** -- [ ] Found and analyzed similar component's test file in other use case -- [ ] Identified render helper patterns from similar component -- [ ] Noted locator/assertion patterns from similar component +- [ ] Found and analyzed reference component's test file (similar component in other use case or equivalent template component) **Properties:** - [ ] Valid props → no error - [ ] Invalid props → error with specific message -**Controller Integration:** +**Controller Integration (Molecules/Enzymes):** - [ ] Controller built with engine - [ ] State bound via `@bindStateToController` - [ ] Methods called on user interaction +**Result/Product Template Components:** +- [ ] Mock `ResultTemplatesHelpers.getResultProperty` for field access +- [ ] Test with valid result/product data +- [ ] Test field value parsing (if applicable) +- [ ] Test self-removal behavior for null/invalid values (if applicable) +- [ ] Test custom event handling (if component dispatches/listens) + **Rendering:** - [ ] Shadow parts present/conditional - [ ] Slotted content @@ -251,7 +326,3 @@ const mockController = {state: {}, subscribe: vi.fn()}; ## Post-Execution Summary Generate execution summary at `.github/prompts/.executions/generate-vitest-tests-[component]-[YYYY-MM-DD-HHmmss].prompt-execution.md` following `TEMPLATE.prompt-execution.md`. Include reference component used, issues encountered, ambiguities requiring interpretation, and concrete improvement suggestions. Inform user of summary location. - - -```` -``` diff --git a/.github/prompts/migrate-stencil-to-lit.prompt.md b/.github/prompts/migrate-stencil-to-lit.prompt.md index 633f5362c5a..48c6dda3d4d 100644 --- a/.github/prompts/migrate-stencil-to-lit.prompt.md +++ b/.github/prompts/migrate-stencil-to-lit.prompt.md @@ -101,8 +101,42 @@ Migrate files according to this mapping: - Convert lifecycle methods (componentWillLoad → willUpdate, etc.) - Replace CSS classes with Tailwind CSS utilities - **Use Lit reactive controllers instead of Stencil context providers** (e.g., ProductContext should be a reactive controller) +- **Result template components:** Replace Stencil `@ResultContext()` decorator with `createResultContextController`: + ```typescript + import {createResultContextController} from '@/src/components/search/result-template-component-utils/context/result-context-controller'; + + private resultContext = createResultContextController(this); + private get result(): Result { + return this.resultContext.item as Result; + } + ``` - **Use Lit's `nothing` directive instead of `null` for conditional rendering** +**Light DOM Components:** + +Light DOM components render without Shadow DOM for styling integration. Use `LightDomMixin` for this pattern. + +```typescript +import {LightDomMixin} from '@/src/mixins/light-dom'; + +// Basic light DOM component +@customElement('atomic-result-number') +@bindings() +export class AtomicResultNumber + extends LightDomMixin(LitElement) + implements InitializableComponent + +// Light DOM component requiring bindings initialization +@customElement('atomic-icon') +export class AtomicIcon + extends LightDomMixin(InitializeBindingsMixin(LitElement)) + implements InitializableComponent +``` + +**When to use:** +- `LightDomMixin(LitElement)` - Most light DOM components with `@bindings()` decorator +- `LightDomMixin(InitializeBindingsMixin(LitElement))` - Components needing binding initialization logic (e.g., components without use case-specific bindings) + **Import Path Migration (CRITICAL):** **Rule:** Replace ALL `../` imports with `@/src/` path aliases. @@ -330,21 +364,27 @@ return condition ? html`
Content
` : nothing; html`
${shouldShow ? html`Content` : nothing}
`; ``` -## Important Constraints +**❌ Missing result context controller:** -**Do not do the following unless explicitly asked by the user:** +```typescript +// DON'T: Try to access result without controller +@ResultContext() result!: Result; // Stencil pattern +``` + +**✅ Correct result context setup:** + +```typescript +import {createResultContextController} from '@/src/components/search/result-template-component-utils/context/result-context-controller'; -- Do not build the Atomic package -- Do not run tests or generate new tests -- Do not fix linting issues (save linting fixes for after migration is complete) -- Do not modify existing test files (`.spec.ts`, `e2e/` files) -- Do not check if generation scripts succeeded or look for generated files +private resultContext = createResultContextController(this); +private get result(): Result { + return this.resultContext.item as Result; +} +``` -**Focus only on:** +## Important Constraints -- Code migration from Stencil to Lit -- Style migration from PostCSS to Tailwind -- Functional component/utility migration +Focus on code/style/functional migration only. Do not: build, test, lint, modify test files, or verify generation script outputs (unless user requests). ## Migration Checklist @@ -404,5 +444,3 @@ After completing migration, generate execution summary: **4. Inform user** about summary location **5. Mark complete** only after file created and user informed. - -``` diff --git a/.github/prompts/write-atomic-component-mdx-documentation.prompt.md b/.github/prompts/write-atomic-component-mdx-documentation.prompt.md index 525f3dc0d0b..098746f7b85 100644 --- a/.github/prompts/write-atomic-component-mdx-documentation.prompt.md +++ b/.github/prompts/write-atomic-component-mdx-documentation.prompt.md @@ -30,19 +30,20 @@ MDX files provide user-facing documentation in Storybook. They should: - `atomic-query-error` → `query-error` - `atomic-product-link` → `product-link` -2. **Search for similar components across use cases:** +2. **Fast-path checks (skip broad search if true):** + - Component path contains `result-template-components` or `product-template-components` → Use `atomic-product-link.mdx` as reference + - Component is in `/common` folder → Skip use-case search, go to template pattern lookup + +3. **Search for similar components (only if no fast-path match):** ```bash find packages/atomic/src/components -name "*${base-name}*.mdx" ``` -3. **Check these use case folders:** - - `/search` - Search interface components - - `/commerce` - Commerce/product listing components - - `/insight` - Insight panel components - - `/recommendations` - Recommendation components - - `/common` - Shared components +4. **Limit search scope:** + - If component has use-case prefix (e.g., `atomic-commerce-*`), only search that use case folder + - For non-prefixed components in `/search`, `/commerce`, `/insight`, or `/recommendations`, skip other use-case folders -4. **Verify MDX exists and is complete:** +5. **Verify MDX exists and is complete:** - Has more than just TODO comments - Contains actual usage examples - Shows the component within its interface context @@ -110,6 +111,8 @@ import { AtomicDocTemplate } from '../../../../storybook-utils/documentation/ato ### Component Description +**The `AtomicDocTemplate` automatically renders the component's JSDoc.** Only add description if it provides usage context beyond JSDoc (e.g., typical placement, relationships). + Start with: "The `atomic-component-name` component [description]." **Examples:** @@ -120,36 +123,58 @@ Start with: "The `atomic-component-name` component [description]." Always show the component within its proper interface hierarchy: -**Search components:** +**Template components** (used within product/result templates): + +**CRITICAL:** Template components MUST be nested inside a `