ripple 0.2.91 → 0.2.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,167 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mount, Portal, track, flushSync } from 'ripple';
3
+
4
+ describe('Portal', () => {
5
+ let container;
6
+
7
+ function render(component) {
8
+ mount(component, {
9
+ target: container,
10
+ });
11
+ }
12
+
13
+ beforeEach(() => {
14
+ container = document.createElement('div');
15
+ document.body.appendChild(container);
16
+ });
17
+
18
+ afterEach(() => {
19
+ // Remove container
20
+ document.body.removeChild(container);
21
+
22
+ // Clean up any leftover portal content from document.body
23
+ const portals = document.body.querySelectorAll('.test-portal');
24
+ portals.forEach(el => el.remove());
25
+ });
26
+
27
+ it('renders portal content to target element', () => {
28
+ const target = document.createElement('div');
29
+ document.body.appendChild(target);
30
+
31
+ component TestPortal() {
32
+ <Portal target={target}><div class='test-portal'>{'Portal works!'}</div></Portal>
33
+ }
34
+
35
+ render(TestPortal);
36
+
37
+ // Portal content should be in the target, not in container
38
+ expect(container.querySelector('.test-portal')).toBeNull();
39
+ expect(target.querySelector('.test-portal')).toBeTruthy();
40
+ expect(target.querySelector('.test-portal').textContent).toBe('Portal works!');
41
+
42
+ document.body.removeChild(target);
43
+ });
44
+
45
+ it('renders portal content to document.body', () => {
46
+ component TestPortal() {
47
+ <Portal target={document.body}><div class='test-portal'>{'In document.body!'}</div></Portal>
48
+ }
49
+
50
+ render(TestPortal);
51
+
52
+ // Should not be in container
53
+ expect(container.querySelector('.test-portal')).toBeNull();
54
+
55
+ // Should be in document.body
56
+ expect(document.body.querySelector('.test-portal')).toBeTruthy();
57
+ expect(document.body.querySelector('.test-portal').textContent).toBe('In document.body!');
58
+ });
59
+
60
+ it('cleans up portal content when destroyed via conditional rendering', () => {
61
+ component TestPortal() {
62
+ let open = track(true);
63
+
64
+ if (@open) {
65
+ <Portal target={document.body}><div class='test-portal'>{'Conditional content'}</div></Portal>
66
+ }
67
+
68
+ <button onClick={() => @open = false}>{'Close'}</button>
69
+ }
70
+
71
+ render(TestPortal);
72
+
73
+ // Initially portal content should be present
74
+ expect(document.body.querySelector('.test-portal')).toBeTruthy();
75
+
76
+ // Click close button to destroy portal
77
+ container.querySelector('button').click();
78
+ flushSync();
79
+
80
+ // Portal content should be cleaned up
81
+ expect(document.body.querySelector('.test-portal')).toBeNull();
82
+ });
83
+
84
+ it('opens and closes portal via conditional rendering', () => {
85
+ component TestPortal() {
86
+ let open = track(false);
87
+
88
+ if (@open) {
89
+ <Portal target={document.body}><div class='test-portal'>
90
+ {'Content'}
91
+ <button onClick={() => @open = false}>{'Close'}</button>
92
+ </div></Portal>
93
+ }
94
+
95
+ if (!@open) {
96
+ <button onClick={() => @open = true}>{'Open'}</button>
97
+ }
98
+ }
99
+
100
+ render(TestPortal);
101
+
102
+ // Open the portal
103
+ container.querySelector('button').click();
104
+ flushSync();
105
+ expect(document.body.querySelector('.test-portal')).toBeTruthy();
106
+
107
+ // Close the portal - this should work without errors
108
+ expect(() => {
109
+ document.body.querySelector('button').click();
110
+ flushSync();
111
+ }).not.toThrow();
112
+
113
+ // Portal content should be cleaned up
114
+ expect(document.body.querySelector('.test-portal')).toBeNull();
115
+ });
116
+
117
+ it('handles multiple portals simultaneously', () => {
118
+ const target1 = document.createElement('div');
119
+ const target2 = document.createElement('div');
120
+ target1.id = 'multi-target1';
121
+ target2.id = 'multi-target2';
122
+ document.body.appendChild(target1);
123
+ document.body.appendChild(target2);
124
+
125
+ component TestMultiPortal() {
126
+ <Portal target={target1}><div class='test-portal'>{'Portal 1 content'}</div></Portal>
127
+
128
+ <Portal target={target2}><div class='test-portal'>{'Portal 2 content'}</div></Portal>
129
+ }
130
+
131
+ render(TestMultiPortal);
132
+
133
+ // Both portals should render in their respective targets
134
+ expect(target1.querySelector('.test-portal')).toBeTruthy();
135
+ expect(target1.querySelector('.test-portal').textContent).toBe('Portal 1 content');
136
+
137
+ expect(target2.querySelector('.test-portal')).toBeTruthy();
138
+ expect(target2.querySelector('.test-portal').textContent).toBe('Portal 2 content');
139
+
140
+ document.body.removeChild(target1);
141
+ document.body.removeChild(target2);
142
+ });
143
+
144
+ it('handles portal with reactive content', () => {
145
+ component TestReactivePortal() {
146
+ let count = track(0);
147
+
148
+ <Portal target={document.body}><div class='test-portal'>
149
+ {'Count: '}
150
+ {String(@count)}
151
+ <button onClick={() => @count++}>{'Increment'}</button>
152
+ </div></Portal>
153
+ }
154
+
155
+ render(TestReactivePortal);
156
+
157
+ const portalElement = document.body.querySelector('.test-portal');
158
+ expect(portalElement).toBeTruthy();
159
+ expect(portalElement.textContent).toContain('Count: 0');
160
+
161
+ // Click increment button
162
+ portalElement.querySelector('button').click();
163
+ flushSync();
164
+
165
+ expect(portalElement.textContent).toContain('Count: 1');
166
+ });
167
+ });
package/types/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export declare function mount(
5
5
  options: { target: HTMLElement; props?: Record<string, any> },
6
6
  ): () => void;
7
7
 
8
+ export declare function tick(): Promise<void>;
9
+
8
10
  export declare function untrack<T>(fn: () => T): T;
9
11
 
10
12
  export declare function flushSync<T>(fn: () => T): T;