Skip to content

Commit 5a75b81

Browse files
TanmayRanawaresilky-x0TanmayRanaware
authored
Unit test closed sink (#40)
* Added more info for newcomers * Rebase * Fix heading * Fix heading * Resolve merge conflict in swap-source.test.ts during rebase * Add comprehensive unit test suite for Closed sink - Created BDD-style test suite following existing patterns - Tests cover ClosedSink function and Closed explicit sink - Includes dialog element closing functionality tests - Tests boolean attribute value handling (true/false) - Covers future/promise and observable sources - Includes edge cases and integration scenarios - Validates sink configuration structure - Uses custom MockDialogElement for HTMLDialogElement testing * Resolve merge conflict in swap-source.test.ts * Remove TL;DR reference from README and delete QUICKSTART.md - Remove 'To Busy to read?, Check [TL;DR 📜](./QUICKSTART.md)' line from README - Delete QUICKSTART.md file completely - Clean up documentation structure * Refactor closed-sink tests to follow one action => one reaction principle - Split multiple actions into individual tests - Each test now has clear cause and effect - Better test isolation and debugging - More descriptive test names - Follows best practices for test organization * Clarify closed-sink test descriptions to reflect actual behavior - Change 'closes dialog' to 'calls dialog.close()' in test names - Add comments explaining dialog.close() has no parameters - Clarify that there's no 'unclose' functionality - Each call to sink() results in dialog.close() being called regardless of input * Add shared test helper for sink binding configuration assertions - Add expectSinkBindingConfiguration helper function to test-support.ts - Replace repetitive sink configuration assertions with shared helper - Improve test maintainability and reduce code duplication - Helper can be reused across all sink tests * Fix closed-sink tests and remove unrelated swap-source.test.ts - Remove parameters from ClosedSink calls (sink doesn't take parameters) - Remove redundant test for source reference preservation - Remove unrelated swap-source.test.ts file from closed-sink PR - Simplify tests to focus on actual ClosedSink behavior - Clean up test structure and remove unnecessary complexity * Restore swap-source.test.ts from master branch This file exists in master but was accidentally removed from this branch. Restoring it to maintain consistency with master branch. * Simplify closed-sink tests by removing redundant cases - Remove redundant test for basic sink invocation - Remove redundant test for multiple calls - Remove redundant test for separate bindings - Keep only essential tests that add value - Focus on core functionality rather than exhaustive parameter testing --------- Co-authored-by: 3mindedscholar <10akhil.t@gmail.com> Co-authored-by: TanmayRanaware <tanmayranware2000@gmail.com>
1 parent 5375475 commit 5a75b81

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

src/sinks/closed-sink.test.ts

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import { MockElement, expectSinkBindingConfiguration } from '../test-support';
2+
import { Closed, ClosedSink, CLOSED_SINK_TAG } from './closed-sink';
3+
import { SINK_TAG } from '../constants';
4+
5+
// Mock HTMLDialogElement for testing
6+
interface MockDialogElement extends HTMLElement {
7+
open: boolean;
8+
close: jest.MockedFunction<() => void>;
9+
show: jest.MockedFunction<() => void>;
10+
showModal: jest.MockedFunction<() => void>;
11+
}
12+
13+
const MockDialogElement = (props?: Record<string, any>): MockDialogElement => {
14+
const closeSpy = jest.fn();
15+
const showSpy = jest.fn();
16+
const showModalSpy = jest.fn();
17+
18+
return <MockDialogElement>{
19+
...MockElement(props),
20+
open: false,
21+
close: closeSpy,
22+
show: showSpy,
23+
showModal: showModalSpy,
24+
tagName: 'DIALOG',
25+
...props,
26+
};
27+
};
28+
29+
describe('Closed Sink', () => {
30+
31+
describe('Given a ClosedSink function', () => {
32+
33+
it('creates a sink that calls dialog.close() when called', () => {
34+
const dialog = MockDialogElement();
35+
const sink = ClosedSink(dialog);
36+
37+
expect(typeof sink).toBe('function');
38+
expect(dialog.close).not.toHaveBeenCalled();
39+
40+
// Each call to sink() results in dialog.close() being called
41+
sink();
42+
expect(dialog.close).toHaveBeenCalledTimes(1);
43+
44+
sink();
45+
expect(dialog.close).toHaveBeenCalledTimes(2);
46+
});
47+
48+
it('binds the close method correctly to dialog element', () => {
49+
const dialog = MockDialogElement();
50+
const sink = ClosedSink(dialog);
51+
52+
sink();
53+
54+
expect(dialog.close).toHaveBeenCalledWith();
55+
expect(dialog.close).toHaveBeenCalledTimes(1);
56+
});
57+
58+
});
59+
60+
describe('Given a Closed explicit sink', () => {
61+
62+
it('creates sink binding configuration with correct properties', () => {
63+
const source = true;
64+
const config = Closed(source);
65+
66+
expectSinkBindingConfiguration(config, SINK_TAG, CLOSED_SINK_TAG, source, ClosedSink);
67+
});
68+
69+
it('handles boolean attribute values', () => {
70+
const trueConfig = Closed(true);
71+
const falseConfig = Closed(false);
72+
73+
expect(trueConfig.source).toBe(true);
74+
expect(falseConfig.source).toBe(false);
75+
});
76+
77+
it('handles promise sources', () => {
78+
const promiseSource = Promise.resolve(true);
79+
const config = Closed(promiseSource);
80+
81+
expect(config.source).toBe(promiseSource);
82+
});
83+
84+
it('handles observable sources', () => {
85+
const { Subject } = require('rxjs');
86+
const subjectSource = new Subject();
87+
const config = Closed(subjectSource);
88+
89+
expect(config.source).toBe(subjectSource);
90+
});
91+
92+
});
93+
94+
describe('Given dialog closing functionality', () => {
95+
96+
it('closes dialog when sink is invoked', () => {
97+
const dialog = MockDialogElement();
98+
const config = Closed(true);
99+
const sink = config.sink(dialog);
100+
101+
sink();
102+
103+
expect(dialog.close).toHaveBeenCalledTimes(1);
104+
});
105+
106+
});
107+
108+
describe('Given future/promise sources', () => {
109+
110+
it('handles resolved promise sources', async () => {
111+
const dialog = MockDialogElement();
112+
const promiseSource = Promise.resolve(true);
113+
const config = Closed(promiseSource);
114+
const sink = config.sink(dialog);
115+
116+
sink();
117+
118+
// Wait for promise to resolve
119+
await new Promise(resolve => setTimeout(resolve, 10));
120+
121+
expect(dialog.close).toHaveBeenCalledTimes(1);
122+
});
123+
124+
it('handles rejected promise sources', async () => {
125+
const dialog = MockDialogElement();
126+
const promiseSource = Promise.reject(new Error('Test error'));
127+
const config = Closed(promiseSource);
128+
const sink = config.sink(dialog);
129+
130+
sink();
131+
132+
// Wait for promise to reject
133+
await new Promise(resolve => setTimeout(resolve, 10));
134+
135+
expect(dialog.close).toHaveBeenCalledTimes(1);
136+
});
137+
138+
});
139+
140+
describe('Given observable sources', () => {
141+
142+
it('handles Subject sources', () => {
143+
const { Subject } = require('rxjs');
144+
const subject = new Subject();
145+
const config = Closed(subject);
146+
147+
expect(config.source).toBe(subject);
148+
expect(config.type).toBe(SINK_TAG);
149+
expect(config.t).toBe(CLOSED_SINK_TAG);
150+
});
151+
152+
it('handles BehaviorSubject sources', () => {
153+
const { BehaviorSubject } = require('rxjs');
154+
const behaviorSubject = new BehaviorSubject(false);
155+
const config = Closed(behaviorSubject);
156+
157+
expect(config.source).toBe(behaviorSubject);
158+
});
159+
160+
});
161+
162+
describe('Given edge cases', () => {
163+
164+
it('handles complex object sources', () => {
165+
const dialog = MockDialogElement();
166+
const complexSource = { nested: { value: true } };
167+
const config = Closed(complexSource);
168+
169+
expect(config.source).toBe(complexSource);
170+
});
171+
172+
it('handles array sources', () => {
173+
const dialog = MockDialogElement();
174+
const arraySource = [true, false, 1, 0];
175+
const config = Closed(arraySource);
176+
177+
expect(config.source).toBe(arraySource);
178+
});
179+
180+
});
181+
182+
describe('Given sink configuration structure', () => {
183+
184+
it('returns correct sink binding configuration type', () => {
185+
const source = true;
186+
const config = Closed(source);
187+
188+
expectSinkBindingConfiguration(config, SINK_TAG, CLOSED_SINK_TAG, source, ClosedSink);
189+
});
190+
191+
it('maintains proper TypeScript types', () => {
192+
const source = true;
193+
const config = Closed(source);
194+
195+
// These should compile without errors if types are correct
196+
expect(typeof config.type).toBe('string');
197+
expect(typeof config.t).toBe('string');
198+
expect(typeof config.sink).toBe('function');
199+
});
200+
201+
});
202+
203+
describe('Given integration scenarios', () => {
204+
205+
it('works with multiple dialog elements', () => {
206+
const dialog1 = MockDialogElement();
207+
const dialog2 = MockDialogElement();
208+
209+
const sink1 = ClosedSink(dialog1);
210+
const sink2 = ClosedSink(dialog2);
211+
212+
sink1();
213+
sink2();
214+
215+
expect(dialog1.close).toHaveBeenCalledTimes(1);
216+
expect(dialog2.close).toHaveBeenCalledTimes(1);
217+
});
218+
219+
});
220+
221+
});

src/test-support.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,17 @@ export const MockEvent = <E extends Event>(name: string, data: Partial<E>) =>
101101
type: name,
102102
...data,
103103
});
104+
105+
// Sink binding configuration test helper
106+
export const expectSinkBindingConfiguration = (
107+
config: any,
108+
expectedType: string,
109+
expectedTag: string,
110+
expectedSource: any,
111+
expectedSink: any
112+
) => {
113+
expect(config.type).toBe(expectedType);
114+
expect(config.t).toBe(expectedTag);
115+
expect(config.source).toBe(expectedSource);
116+
expect(config.sink).toBe(expectedSink);
117+
};

0 commit comments

Comments
 (0)