From d6594a48c2558a10faa6678a9b427e76022428a1 Mon Sep 17 00:00:00 2001 From: Nora Simonow Date: Tue, 28 Oct 2025 16:28:08 +0100 Subject: [PATCH 1/2] fix: prevent catChange event emission on select initialization Fixes #802 to prevent event emission during component initialization/resolution - Only emit catChange when selection changes due to user interaction - Add spec test to verify no event emission on initialization - Add spec test to verify event emission on user interaction - Add e2e test for initialization behavior --- .../components/cat-select/cat-select.e2e.ts | 30 ++++++++++++ .../components/cat-select/cat-select.spec.tsx | 46 +++++++++++++++++++ core/src/components/cat-select/cat-select.tsx | 2 +- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/core/src/components/cat-select/cat-select.e2e.ts b/core/src/components/cat-select/cat-select.e2e.ts index 4a7762b80..1b5882622 100644 --- a/core/src/components/cat-select/cat-select.e2e.ts +++ b/core/src/components/cat-select/cat-select.e2e.ts @@ -13,4 +13,34 @@ describe('cat-select', () => { const element = await page.find('cat-select'); expect(element).toHaveClass('hydrated'); }); + + it('should not emit catChange event on initialization with value', async () => { + const page = await newE2EPage(); + await page.setContent(` + + + `); + + const select = await page.find('cat-select'); + const changeSpy = await select.spyOnEvent('catChange'); + + await page.waitForChanges(); + + expect(changeSpy).not.toHaveReceivedEvent(); + }); }); diff --git a/core/src/components/cat-select/cat-select.spec.tsx b/core/src/components/cat-select/cat-select.spec.tsx index 10b5516bc..54f26849d 100644 --- a/core/src/components/cat-select/cat-select.spec.tsx +++ b/core/src/components/cat-select/cat-select.spec.tsx @@ -2,6 +2,7 @@ jest.mock('../cat-i18n/cat-i18n-registry'); import { newSpecPage } from '@stencil/core/testing'; import { CatSelect } from './cat-select'; +import { stringArrayConnector } from './connectors'; describe('cat-select', () => { it('renders', async () => { @@ -13,4 +14,49 @@ describe('cat-select', () => { `); }); + + describe('catChange', () => { + it('should not emit catChange event on initialization with value', async () => { + const page = await newSpecPage({ + components: [CatSelect], + html: `` + }); + + const select = page.rootInstance as CatSelect; + const catChangeSpy = jest.fn(); + + page.root?.addEventListener('catChange', catChangeSpy); + + await select.connect(stringArrayConnector(['option1', 'option2', 'option3'])); + await page.waitForChanges(); + + expect(catChangeSpy).not.toHaveBeenCalled(); + }); + + it('should emit catChange event when selection state changes from user interaction', async () => { + const page = await newSpecPage({ + components: [CatSelect], + html: `` + }); + + const select = page.rootInstance as CatSelect; + let eventEmitted = false; + + await select.connect(stringArrayConnector(['option1', 'option2', 'option3'])); + await page.waitForChanges(); + + page.root?.addEventListener('catChange', () => { + eventEmitted = true; + }); + + // Directly update selection state (simulating what happens after user interaction) + select['patchState']({ + selection: [{ item: { id: 'option1' }, render: { label: 'option1' } }], + tempSelection: [] + }); + await page.waitForChanges(); + + expect(eventEmitted).toBe(true); + }); + }); }); diff --git a/core/src/components/cat-select/cat-select.tsx b/core/src/components/cat-select/cat-select.tsx index 398e1fbc3..4de9b7a94 100644 --- a/core/src/components/cat-select/cat-select.tsx +++ b/core/src/components/cat-select/cat-select.tsx @@ -339,8 +339,8 @@ export class CatSelect { if (!oldState.isResolving) { this.valueChangedBySelection = true; this.value = newValue; + this.catChange.emit(); } - this.catChange.emit(); this.showErrorsIfTimeout(); } } From 4b3e3c3fb2b7c7efea0330e6443dbd40b3ddeafc Mon Sep 17 00:00:00 2001 From: Nora Simonow Date: Tue, 28 Oct 2025 16:44:03 +0100 Subject: [PATCH 2/2] test: add test for programmatic value changes - Verify that catChange is not emitted for programmatic value changes - Only user interactions should trigger catChange events - Programmatic changes trigger resolve() which sets isResolving=true --- .../components/cat-select/cat-select.spec.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/src/components/cat-select/cat-select.spec.tsx b/core/src/components/cat-select/cat-select.spec.tsx index 54f26849d..fbecb6a23 100644 --- a/core/src/components/cat-select/cat-select.spec.tsx +++ b/core/src/components/cat-select/cat-select.spec.tsx @@ -50,6 +50,7 @@ describe('cat-select', () => { }); // Directly update selection state (simulating what happens after user interaction) + // This mimics the internal flow when user clicks an option select['patchState']({ selection: [{ item: { id: 'option1' }, render: { label: 'option1' } }], tempSelection: [] @@ -58,5 +59,25 @@ describe('cat-select', () => { expect(eventEmitted).toBe(true); }); + + it('should not emit catChange event when value is changed programmatically', async () => { + const page = await newSpecPage({ + components: [CatSelect], + html: `` + }); + + const select = page.rootInstance as CatSelect; + const catChangeSpy = jest.fn(); + + await select.connect(stringArrayConnector(['option1', 'option2', 'option3'])); + await page.waitForChanges(); + + page.root?.addEventListener('catChange', catChangeSpy); + + select.value = 'option2'; + await page.waitForChanges(); + + expect(catChangeSpy).not.toHaveBeenCalled(); + }); }); });