From 178c04334bcee081b3eae586d411dc26e2d10af4 Mon Sep 17 00:00:00 2001 From: Pradeep Kaswan Date: Wed, 1 Oct 2025 13:20:30 +0530 Subject: [PATCH] test(sources): add and refine Swap source unit tests; cover default replacement and remove duplicates; fix swap JSDoc (closes #33) --- src/sources/swap-source.test.ts | 157 ++++++++++++++++++++++++++++++++ src/sources/swap-source.ts | 17 ++-- 2 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 src/sources/swap-source.test.ts diff --git a/src/sources/swap-source.test.ts b/src/sources/swap-source.test.ts new file mode 100644 index 0000000..20af546 --- /dev/null +++ b/src/sources/swap-source.test.ts @@ -0,0 +1,157 @@ +import type { Observable } from 'rxjs'; + +import { Subject } from 'rxjs'; +import { Swap, swap } from './swap-source'; +import { MockElement, MockEvent } from '../test-support'; + +describe('Swap Event Adapter', () => { + it('Swaps a value from an element with a static string', () => { + const oldValue = 'old data'; + const newValue = 'new data'; + const el = MockElement({ + tagName: "INPUT", + type: 'text', + value: oldValue, + }); + const eventData = MockEvent('input', { + target: el as HTMLInputElement + }); + const handlerSpy = jest.fn(); + const source = Swap(newValue, handlerSpy); + + source.next(eventData); + + expect(handlerSpy).toHaveBeenCalledWith(oldValue); + expect(el.value).toEqual(newValue); + }) +}) + +it('Swaps a value from an element with empty string by default', () => { + const oldValue = 'old data'; + const el = MockElement({ + tagName: 'INPUT', + type: 'text', + value: oldValue, + }); + const eventData = MockEvent('input', { + target: el as HTMLInputElement + }); + const handlerSpy = jest.fn(); + const source = Swap(undefined, handlerSpy); + + source.next(eventData); + + expect(handlerSpy).toHaveBeenCalledWith(oldValue); + expect(el.value).toEqual(''); +}); + +it('Swaps a value using a function that transforms based on the old value', () => { + const oldValue = '5'; + const replacementFn = (v: string) => String(Number(v) * 2); + const el = MockElement({ + tagName: 'INPUT', + type: 'text', + value: oldValue, + }); + const eventData = MockEvent('input', { + target: el as HTMLInputElement + }); + const handlerSpy = jest.fn(); + const source = Swap(replacementFn, handlerSpy); + + source.next(eventData); + + expect(handlerSpy).toHaveBeenCalledWith(oldValue); + expect(el.value).toEqual('10'); +}); + +describe('swap Event Operator', () => { + it('Swaps and emits a value from an element with a static string', () => { + const oldValue = 'old data'; + const newValue = 'new data'; + const el = MockElement({ + tagName: 'INPUT', + type: 'text', + value: oldValue, + }); + const eventData = MockEvent('input', { + target: el as HTMLInputElement + }); + const handlerSpy = jest.fn(); + const pipeline = new Subject().pipe(swap(newValue)) as Observable & Subject; + + pipeline.subscribe(x => handlerSpy(x)); + pipeline.next(eventData); + + expect(handlerSpy).toHaveBeenCalledWith(oldValue); + expect(el.value).toEqual(newValue); + }); + + it('Swaps and emits a value from an element with empty string', () => { + const oldValue = 'old data'; + const el = MockElement({ + tagName: 'INPUT', + type: 'text', + value: oldValue, + }); + const eventData = MockEvent('input', { + target: el as HTMLInputElement + }); + const handlerSpy = jest.fn(); + const pipeline = new Subject().pipe(swap('')) as Observable & Subject; + + pipeline.subscribe(x => handlerSpy(x)); + pipeline.next(eventData); + + expect(handlerSpy).toHaveBeenCalledWith(oldValue); + expect(el.value).toEqual(''); + }); + + + + it('Swaps a value using a function that generates new value from old', () => { + const oldValue = 'test'; + const replacementFn = (v: string) => `${v}_modified`; + const el = MockElement({ + tagName: 'INPUT', + type: 'text', + value: oldValue, + }); + const eventData = MockEvent('input', { + target: el as HTMLInputElement + }); + const handlerSpy = jest.fn(); + const pipeline = new Subject().pipe(swap(replacementFn)) as Observable & Subject; + + pipeline.subscribe(x => handlerSpy(x)); + pipeline.next(eventData); + + expect(handlerSpy).toHaveBeenCalledWith(oldValue); + expect(el.value).toEqual('test_modified'); + }); + + it('Handles multiple swap operations in sequence', () => { + const values = ['first', 'second', 'third']; + const el = MockElement({ + tagName: 'INPUT', + type: 'text', + value: values[0], + }); + const handlerSpy = jest.fn(); + const pipeline = new Subject().pipe(swap('replacement')) as Observable & Subject; + + pipeline.subscribe(x => handlerSpy(x)); + + values.forEach(val => { + el.value = val; + const eventData = MockEvent('input', { target: el as HTMLInputElement }); + pipeline.next(eventData); + }); + + expect(handlerSpy).toHaveBeenCalledTimes(3); + expect(handlerSpy).toHaveBeenNthCalledWith(1, 'first'); + expect(handlerSpy).toHaveBeenNthCalledWith(2, 'second'); + expect(handlerSpy).toHaveBeenNthCalledWith(3, 'third'); + expect(el.value).toEqual('replacement'); + }); +}); diff --git a/src/sources/swap-source.ts b/src/sources/swap-source.ts index 39615ee..90d1abd 100644 --- a/src/sources/swap-source.ts +++ b/src/sources/swap-source.ts @@ -7,10 +7,11 @@ import { curry } from '../utils/curry'; import { EventListenerFunction } from '../types/dom'; /** - * An Event Source Operator that "cuts" the value of the underlying element - * and resets it to the provided value or empty otherwise - * @param handler A handler function or observer to send events to - * @returns EventSource + * An Event Operator that swaps the value of the underlying element + * with the provided replacement (or empty string by default) and emits the previous value. + * This operator mutates the element's value as a side effect. + * @param replacement A string or function used to compute the new value + * @returns OperatorFunction */ export const swap = (replacement: string | Function) => map((e: E) => { @@ -22,10 +23,10 @@ export const swap = (replacement: string | Function) => ; /** - * An Event Source that "cuts" the value of the underlying <input> element - * and resets it to the provided value or empty otherwise - * @param replacement A new value to swap the current element's value with - * @param source A handler function or observer to send events to + * An Event Adapter that swaps the value of the underlying <input> element + * with the provided replacement (or empty string by default) and emits the previous value to the given target. + * @param replacement A new value or function to compute the element's next value + * @param source A handler function or observer to send emitted values to * @returns EventSource */ export const Swap =