Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/agents/stencil-to-lit-migration-v1.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,15 @@ When opening a PR for a functional component migration:

Before completing a functional component migration, verify:

**Test quality (consensus-based patterns):**
- [ ] Icon properties tested using bracket notation (`iconElement?.['icon']`)
- [ ] Only conditional CSS classes tested (static classes skipped)
- [ ] Children content verified via text content (DOM checks optional)
- [ ] `tw`/`multiClassMap` used only for conditional/dynamic classes
- [ ] Interactive tests use Page API (`page.getByRole()`, not DOM API)
- [ ] Edge cases focus on security and common scenarios (pragmatic scope)

**Migration equivalence:**
- [ ] Functional component migrated to Lit with correct types
- [ ] All imports use `@/*` path aliases (no `../` imports)
- [ ] Original Stencil file renamed with `stencil-` prefix
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ it('should handle click', async () => {

### 5. Essential Coverage

Test: (1) Basic rendering, (2) All props/defaults, (3) User interactions, (4) Visual attributes (classes, ARIA), (5) Children if applicable, (6) Error conditions.
Test: (1) Basic rendering, (2) All props/defaults, (3) User interactions, (4) Conditional visual attributes (CSS classes, ARIA), (5) Children if applicable, (6) Error conditions.

**Note:** Static CSS classes and attributes are covered by visual regression tests. Focus unit tests on conditional logic.

### 6. Key Patterns

Expand Down Expand Up @@ -135,6 +137,25 @@ it('should render localized text', async () => {
});
```

**Icon properties:**
When components accept icon props (from `@/src/components/common/icon/icon`), test using bracket notation:
```typescript
it('should render with correct icon', async () => {
const element = await renderComponent({icon: ArrowUp});
const iconElement = element.querySelector('atomic-icon');
expect(iconElement?.['icon']).toBe(ArrowUp);
});
```

**Children content:**
Verify children via text content. DOM structure verification is optional:
```typescript
it('should render children', async () => {
const element = await renderComponent({}, html`<span>Child content</span>`);
expect(element).toHaveTextContent('Child content'); // Sufficient
});
```

### 7. Run Tests

```bash
Expand Down Expand Up @@ -170,6 +191,38 @@ describe('when disabled', () => {
});
```

**Conditional CSS classes:**
Test classes that change based on props/state. Skip static classes:
```typescript
// ✅ Test conditional classes
it('should apply active class when selected', async () => {
const element = await renderComponent({selected: true});
expect(element.querySelector('button')).toHaveClass('bg-primary');
});

it('should not apply active class when not selected', async () => {
const element = await renderComponent({selected: false});
expect(element.querySelector('button')).not.toHaveClass('bg-primary');
});

// ❌ Skip static classes (covered by visual tests)
it('should have rounded corners', async () => {
const element = await renderComponent();
expect(element.querySelector('button')).toHaveClass('rounded-lg'); // Unnecessary
});
```

**Avoid tw/multiClassMap in test helpers:**
When creating test fixtures, use plain class strings. Reserve `tw`/`multiClassMap` for testing the component's conditional logic, not test setup:
```typescript
// ✅ Good - plain strings in tests
const element = await renderComponent({class: 'test-class'});

// ❌ Avoid - unnecessary complexity in tests
const classNames = tw({'test-class': true});
const element = await renderComponent({class: multiClassMap(classNames)});
```

## Post-Execution Summary

**Mandatory final todo:** Generate `.github/prompts/.executions/generate-vitest-test-atomic-lit-functional-component-[YYYY-MM-DD-HHmmss].prompt-execution.md` following `TEMPLATE.prompt-execution.md`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,44 @@ html`<input ${props.ref ? ref(props.ref) : nothing} />`

## Tailwind Classes

**Use `tw` + `multiClassMap` for conditional classes:**
**Use `tw` + `multiClassMap` only for conditional or dynamic classes.** For components with only static classes, use plain class strings.

**Conditional classes (use tw/multiClassMap):**
```typescript
import {multiClassMap, tw} from '@/src/directives/multi-class-map';

const classNames = tw({
'rounded-lg border p-4': true,
'border-primary bg-primary-50': props.isHighlighted,
'border-neutral-dark bg-white': !props.isHighlighted,
[props.class ?? '']: Boolean(props.class),
[props.class ?? '']: Boolean(props.class), // Dynamic user classes
});

return html`<div class="rounded-lg border p-4 ${multiClassMap(classNames)}">Content</div>`;
```

**Static classes only (plain strings):**
```typescript
// ✅ Good - no conditional logic
return html`<div class="flex items-center gap-2">Content</div>`;

// ❌ Avoid - unnecessary tw/multiClassMap
const classNames = tw({'flex items-center gap-2': true});
return html`<div class=${multiClassMap(classNames)}>Content</div>`;
```

**Mixed static and conditional:**
```typescript
const dynamicClasses = tw({
'bg-primary': props.isPrimary,
'bg-secondary': !props.isPrimary,
});

// Static classes in string, conditional via multiClassMap
return html`<button class="px-4 py-2 rounded ${multiClassMap(dynamicClasses)}">
${props.label}
</button>`;
```

## Naming Conventions

**Files:** Keep original Stencil file (`facet-placeholder.tsx`), create Lit version with `.ts` extension (`facet-placeholder.ts`). Both coexist; imports reference original until consumers migrate.
Expand Down