ripple 0.2.88 → 0.2.89

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,183 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mount, flushSync, TrackedObject, track } from 'ripple';
3
+ import { TRACKED_OBJECT } from '../../src/runtime/internal/client/constants.js';
4
+
5
+ describe('TrackedObject', () => {
6
+ let container;
7
+
8
+ function render(component) {
9
+ mount(component, {
10
+ target: container
11
+ });
12
+ }
13
+
14
+ beforeEach(() => {
15
+ container = document.createElement('div');
16
+ document.body.appendChild(container);
17
+ });
18
+
19
+ afterEach(() => {
20
+ document.body.removeChild(container);
21
+ container = null;
22
+ });
23
+
24
+ it('makes new properties reactive', () => {
25
+ component ObjectTest() {
26
+ const obj = new TrackedObject({});
27
+
28
+ obj.a = 0;
29
+
30
+ <pre>{obj.a}</pre>
31
+ <button onClick={() => { obj.a++; }}>{'Increment A'}</button>
32
+ }
33
+
34
+ render(ObjectTest);
35
+
36
+ const pre1 = container.querySelectorAll('pre')[0];
37
+ const button = container.querySelectorAll('button')[0 ];
38
+
39
+ expect(pre1.textContent).toBe('0');
40
+
41
+ button.click();
42
+ flushSync();
43
+
44
+ expect(pre1.textContent).toBe('1');
45
+ });
46
+
47
+ it('makes existing object properties reactive', () => {
48
+ component ObjectTest() {
49
+ const obj = new TrackedObject({ a: 0 });
50
+
51
+ <pre>{obj.a}</pre>
52
+ <button onClick={() => { obj.a++; }}>{'Increment A'}</button>
53
+ }
54
+
55
+ render(ObjectTest);
56
+
57
+ const pre1 = container.querySelectorAll('pre')[0];
58
+ const button = container.querySelectorAll('button')[0 ];
59
+
60
+ expect(pre1.textContent).toBe('0');
61
+
62
+ button.click();
63
+ flushSync();
64
+
65
+ expect(pre1.textContent).toBe('1');
66
+ });
67
+
68
+ it('checks if property exists via the has trap', () => {
69
+ component ObjectTest() {
70
+ const obj = new TrackedObject({b: 1});
71
+
72
+ obj.a = 0;
73
+
74
+ <pre>{'a' in obj && 'b' in obj}</pre>
75
+ }
76
+
77
+ render(ObjectTest);
78
+
79
+ const pre1 = container.querySelectorAll('pre')[0];
80
+
81
+ expect(pre1.textContent).toBe('true');
82
+ });
83
+
84
+ it('deletes properties via the delete trap', () => {
85
+ component ObjectTest() {
86
+ const obj = new TrackedObject({a: 0, b: 1});
87
+
88
+ <pre>{String(obj.a)}</pre>
89
+ <button onClick={() => { delete obj.a; }}>{'Delete A'}</button>
90
+ }
91
+
92
+ render(ObjectTest);
93
+
94
+ const pre1 = container.querySelectorAll('pre')[0];
95
+ const button = container.querySelectorAll('button')[0 ];
96
+
97
+ expect(pre1.textContent).toBe('0');
98
+
99
+ button.click();
100
+ flushSync();
101
+
102
+ expect(pre1.textContent).toBe('undefined');
103
+ });
104
+
105
+ it('checks if non-existent property is reactive when added later', () => {
106
+ component ObjectTest() {
107
+ const obj = new TrackedObject({});
108
+
109
+ <pre>{String(obj.a)} </pre>
110
+ <button onClick={() => { obj.a = 1; }}>{'Add A'}</button>
111
+ }
112
+
113
+ render(ObjectTest);
114
+
115
+ const pre1 = container.querySelectorAll('pre')[0];
116
+ const button = container.querySelectorAll('button')[0 ];
117
+
118
+ expect(pre1.textContent).toBe('undefined');
119
+
120
+ button.click();
121
+ flushSync();
122
+
123
+ expect(pre1.textContent).toBe('1');
124
+ });
125
+
126
+ it('checks that deeply nested objects are not proxied or reactive', () => {
127
+ component ObjectTest() {
128
+ const obj = new TrackedObject({ a: { b: 1 } });
129
+
130
+ <pre>{String(obj.a.b)}</pre>
131
+ <button onClick={() => { obj.a.b++; }}>{'Increment B'}</button>
132
+ }
133
+
134
+ render(ObjectTest);
135
+
136
+ const pre1 = container.querySelectorAll('pre')[0];
137
+ const button = container.querySelectorAll('button')[0 ];
138
+
139
+ expect(pre1.textContent).toBe('1');
140
+
141
+ button.click();
142
+ flushSync();
143
+
144
+ // remains unchanged
145
+ expect(pre1.textContent).toBe('1');
146
+ });
147
+
148
+ it('checks if TRACKED_OBJECT symbol is present on TrackedObject instances', () => {
149
+ component ObjectTest() {
150
+ const obj = new TrackedObject({ a: 0 });
151
+
152
+ expect(obj[TRACKED_OBJECT]).toBe(true);
153
+ }
154
+ });
155
+
156
+ it('uses the hash syntax for creating TrackedObject', () => {
157
+ component ObjectTest() {
158
+ const obj = #{ a: 0, b: 1, c: { d: {e: 8} } };
159
+
160
+ <pre>{obj.a}</pre>
161
+ <pre>{TRACKED_OBJECT in obj}</pre>
162
+ <pre>{JSON.stringify(obj)}</pre>
163
+ <button onClick={() => { obj.a++; }}>{'Increment A'}</button>
164
+ }
165
+
166
+ render(ObjectTest);
167
+
168
+ const pre1 = container.querySelectorAll('pre')[0];
169
+ const pre2 = container.querySelectorAll('pre')[1];
170
+ const pre3 = container.querySelectorAll('pre')[2];
171
+ const button = container.querySelectorAll('button')[0 ];
172
+
173
+ expect(pre1.textContent).toBe('0');
174
+ expect(pre2.textContent).toBe('true');
175
+ expect(pre3.textContent).toBe('{"a":0,"b":1,"c":{"d":{"e":8}}}');
176
+
177
+ button.click();
178
+ flushSync();
179
+
180
+ expect(pre1.textContent).toBe('1');
181
+ expect(pre3.textContent).toBe('{"a":1,"b":1,"c":{"d":{"e":8}}}');
182
+ })
183
+ });
package/types/index.d.ts CHANGED
@@ -151,3 +151,11 @@ export type TrackedObjectDeep<T> =
151
151
  : T extends object
152
152
  ? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
153
153
  : T | Tracked<T>;
154
+
155
+ export type TrackedObject<T extends object> = T & {};
156
+
157
+ export interface TrackedObjectConstructor {
158
+ new <T extends object>(obj: T): TrackedObject<T>;
159
+ }
160
+
161
+ export declare const TrackedObject: TrackedObjectConstructor;