From 21cd5f6af41921fe3c683fec8995eae31041bb11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:50:57 +0000 Subject: [PATCH 1/2] Initial plan From 8b02ca3b627a28c40661d38447f589d889663cf0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 9 Jul 2025 12:07:50 +0000 Subject: [PATCH 2/2] Fix signal conflicts and migration issues in carousel components Co-authored-by: avs2001 <1026899+avs2001@users.noreply.github.com> --- .../lib/carousel/carousel.component.spec.ts | 4 +-- .../src/lib/carousel/carousel.component.ts | 16 +++++++-- .../lib/carousel/carousel.directive.spec.ts | 35 +++++++++++++++---- .../src/lib/carousel/carousel.directive.ts | 32 ++++++++--------- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/projects/carousel/src/lib/carousel/carousel.component.spec.ts b/projects/carousel/src/lib/carousel/carousel.component.spec.ts index 0f8fe39..4e9ca8e 100644 --- a/projects/carousel/src/lib/carousel/carousel.component.spec.ts +++ b/projects/carousel/src/lib/carousel/carousel.component.spec.ts @@ -10,7 +10,7 @@ import { CarouselScrollDirective } from '../carousel-scroll.directive'; @Component({ standalone: true, template: ` - + Item 1 Item 2 Item 3 @@ -53,7 +53,7 @@ describe('CarouselComponent', () => { }); it('should wrap around when reaching the end', () => { - component.index.set(2); + component.indexValue = 2; fixture.detectChanges(); component.next(); expect(component.index()).toBe(0); diff --git a/projects/carousel/src/lib/carousel/carousel.component.ts b/projects/carousel/src/lib/carousel/carousel.component.ts index 90a538b..6525abd 100644 --- a/projects/carousel/src/lib/carousel/carousel.component.ts +++ b/projects/carousel/src/lib/carousel/carousel.component.ts @@ -5,6 +5,7 @@ import { ContentChildren, HostBinding, Inject, + Input, input, Signal, signal, @@ -34,7 +35,16 @@ import {CarouselDirective} from './carousel.directive'; }) export class CarouselComponent { readonly display = input(1); - readonly index = input(0); + private readonly _index = signal(0); + + readonly index = this._index.asReadonly(); + + @Input() + set indexValue(value: number) { + if (value !== undefined && value !== null) { + this._index.set(value); + } + } @ContentChildren(CAROUSEL_ITEM) readonly items: QueryList = new QueryList(); @@ -78,10 +88,10 @@ export class CarouselComponent { private updateIndex(index: number) { const screens = Math.ceil(this.items.length / this.display()); if (screens - 1 === this.index()) { - this.index.set(0); + this._index.set(0); return; } - this.index.set(clamp(index, 0, screens - 1)); + this._index.set(clamp(index, 0, screens - 1)); this.changeDetectorRef.markForCheck(); } diff --git a/projects/carousel/src/lib/carousel/carousel.directive.spec.ts b/projects/carousel/src/lib/carousel/carousel.directive.spec.ts index 0fac909..de88ed4 100644 --- a/projects/carousel/src/lib/carousel/carousel.directive.spec.ts +++ b/projects/carousel/src/lib/carousel/carousel.directive.spec.ts @@ -1,19 +1,40 @@ -import { ElementRef } from '@angular/core'; -import { fakeAsync, tick } from '@angular/core/testing'; +import { Component, ElementRef } from '@angular/core'; +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { CarouselDirective } from './carousel.directive'; +@Component({ + standalone: true, + template: ``, + imports: [CarouselDirective] +}) +class TestComponent { } + describe('CarouselDirective', () => { + let fixture: ComponentFixture; + let directive: CarouselDirective; + let element: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TestComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(TestComponent); + directive = fixture.debugElement.query(By.directive(CarouselDirective)).injector.get(CarouselDirective); + element = fixture.debugElement.query(By.directive(CarouselDirective)).nativeElement; + fixture.detectChanges(); + }); + it('emits periodically when enabled', fakeAsync(() => { - const div = document.createElement('div'); - const directive = new CarouselDirective(new ElementRef(div)); const values: number[] = []; - directive.duration = 50; + directive.duration = 50; // Set duration via the setter directive.subscribe(() => values.push(values.length)); - div.dispatchEvent(new Event('mouseleave')); + element.dispatchEvent(new Event('mouseleave')); tick(120); expect(values.length).toBeGreaterThan(0); - div.dispatchEvent(new Event('mouseenter')); + element.dispatchEvent(new Event('mouseenter')); const prev = values.length; tick(60); expect(values.length).toBe(prev); diff --git a/projects/carousel/src/lib/carousel/carousel.directive.ts b/projects/carousel/src/lib/carousel/carousel.directive.ts index a7b3865..58b864a 100644 --- a/projects/carousel/src/lib/carousel/carousel.directive.ts +++ b/projects/carousel/src/lib/carousel/carousel.directive.ts @@ -1,4 +1,4 @@ -import {Directive, ElementRef, Inject, Input, signal, computed} from '@angular/core'; +import {Directive, ElementRef, Input, signal, computed, inject} from '@angular/core'; import { toObservable } from '@angular/core/rxjs-interop'; import { EMPTY, @@ -16,33 +16,33 @@ import { }) export class CarouselDirective extends Observable { - private readonly duration = signal(0); + private readonly durationSignal = signal(0); private readonly running = signal(false); - - private readonly output$ = toObservable( - computed(() => ({duration: this.duration(), running: this.running()})) - ).pipe( - switchMap(({duration, running}) => - duration && running ? interval(duration) : EMPTY, - ), - ); + private readonly elementRef = inject(ElementRef); @Input() set duration(duration: number) { - this.duration.set(duration); + this.durationSignal.set(duration); } - constructor( - @Inject(ElementRef) private readonly elementRef: ElementRef, - ) { + constructor() { + // Create output$ in constructor where injection context is available + const output$ = toObservable( + computed(() => ({duration: this.durationSignal(), running: this.running()})) + ).pipe( + switchMap(({duration, running}) => + duration && running ? interval(duration) : EMPTY, + ), + ); + + super(subscriber => output$.subscribe(subscriber)); + merge( fromEvent(this.elementRef.nativeElement, 'mouseenter').pipe(mapTo(false)), fromEvent(this.elementRef.nativeElement, 'touchstart').pipe(mapTo(false)), fromEvent(this.elementRef.nativeElement, 'touchend').pipe(mapTo(true)), fromEvent(this.elementRef.nativeElement, 'mouseleave').pipe(mapTo(true)), ).subscribe(v => this.running.set(v)); - - super(subscriber => this.output$.subscribe(subscriber)); } }