@expressive/mvc 0.53.3 → 0.53.6

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,281 @@
1
+ import { Model } from '../model';
2
+ import { ref } from './ref';
3
+ describe("property", () => {
4
+ it('will contain value from ref-object', async () => {
5
+ class Subject extends Model {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.ref = ref();
9
+ }
10
+ }
11
+ const state = Subject.new();
12
+ state.ref.current = "foobar";
13
+ await expect(state).toHaveUpdated();
14
+ expect(state.ref.current).toBe("foobar");
15
+ });
16
+ it('will get value from ref-object', async () => {
17
+ class Subject extends Model {
18
+ constructor() {
19
+ super(...arguments);
20
+ this.ref = ref();
21
+ }
22
+ }
23
+ const state = Subject.new();
24
+ expect(state.ref.get()).toBeNull();
25
+ state.ref.current = "foobar";
26
+ expect(state.ref.get()).toBe("foobar");
27
+ });
28
+ it('will watch "current" of property', async () => {
29
+ class Subject extends Model {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.ref = ref();
33
+ }
34
+ }
35
+ const state = Subject.new();
36
+ const didCallback = jest.fn();
37
+ state.set((key) => {
38
+ if (key == "ref")
39
+ didCallback();
40
+ });
41
+ state.ref.current = "foobar";
42
+ await expect(state).toHaveUpdated();
43
+ expect(didCallback).toBeCalledWith();
44
+ });
45
+ it('will update "current" when property invoked', async () => {
46
+ class Subject extends Model {
47
+ constructor() {
48
+ super(...arguments);
49
+ this.ref = ref();
50
+ }
51
+ }
52
+ const state = Subject.new();
53
+ const didUpdate = jest.fn();
54
+ state.set((key) => {
55
+ if (key == "ref")
56
+ didUpdate();
57
+ });
58
+ state.ref("foobar");
59
+ await expect(state).toHaveUpdated();
60
+ expect(didUpdate).toBeCalledWith();
61
+ });
62
+ it('will invoke callback', async () => {
63
+ const didTrigger = jest.fn();
64
+ const didUpdate = jest.fn();
65
+ class Subject extends Model {
66
+ constructor() {
67
+ super(...arguments);
68
+ this.ref = ref(didTrigger);
69
+ }
70
+ }
71
+ const state = Subject.new();
72
+ expect(didTrigger).not.toBeCalled();
73
+ state.set(key => {
74
+ if (key == "ref")
75
+ didUpdate();
76
+ });
77
+ state.ref.current = "foobar";
78
+ expect(didTrigger).toBeCalledWith("foobar");
79
+ await expect(state).toHaveUpdated();
80
+ expect(didUpdate).toBeCalledWith();
81
+ });
82
+ it('will invoke return-callback on overwrite', async () => {
83
+ class Subject extends Model {
84
+ constructor() {
85
+ super(...arguments);
86
+ this.ref = ref(() => didTrigger);
87
+ }
88
+ }
89
+ const state = Subject.new();
90
+ const didTrigger = jest.fn();
91
+ state.ref.current = 1;
92
+ await expect(state).toHaveUpdated();
93
+ expect(didTrigger).not.toBeCalled();
94
+ state.ref.current = 2;
95
+ await expect(state).toHaveUpdated();
96
+ expect(didTrigger).toBeCalled();
97
+ });
98
+ it('will not callback when gets null', async () => {
99
+ const callback = jest.fn();
100
+ class Subject extends Model {
101
+ constructor() {
102
+ super(...arguments);
103
+ this.ref = ref(callback);
104
+ }
105
+ }
106
+ const state = Subject.new();
107
+ state.ref("hello");
108
+ expect(callback).toBeCalledWith("hello");
109
+ state.ref(null);
110
+ expect(callback).not.toBeCalledWith(null);
111
+ });
112
+ it('will callback when on null if ignore false', async () => {
113
+ const callback = jest.fn();
114
+ class Subject extends Model {
115
+ constructor() {
116
+ super(...arguments);
117
+ this.ref = ref(callback, false);
118
+ }
119
+ }
120
+ const state = Subject.new();
121
+ state.ref("hello");
122
+ expect(callback).toBeCalledWith("hello");
123
+ state.ref(null);
124
+ expect(callback).toBeCalledWith(null);
125
+ });
126
+ it('will reset nested effects', async () => {
127
+ class Subject extends Model {
128
+ constructor() {
129
+ super(...arguments);
130
+ this.name = "World";
131
+ this.hello = ref((value) => {
132
+ this.get(({ name }) => {
133
+ effect(`${value} ${name}!`);
134
+ });
135
+ });
136
+ }
137
+ }
138
+ const effect = jest.fn();
139
+ const state = Subject.new();
140
+ state.hello("Hola");
141
+ await expect(state).toHaveUpdated();
142
+ expect(effect).toBeCalledWith("Hola World!");
143
+ state.hello("Bonjour");
144
+ await expect(state).toHaveUpdated();
145
+ expect(effect).toBeCalledWith("Bonjour World!");
146
+ state.name = "Earth";
147
+ await expect(state).toHaveUpdated();
148
+ expect(effect).toBeCalledWith("Bonjour Earth!");
149
+ expect(effect).not.toBeCalledWith("Hola Earth!");
150
+ });
151
+ it('will export value of ref-properties', () => {
152
+ class Subject extends Model {
153
+ constructor() {
154
+ super(...arguments);
155
+ this.ref = ref();
156
+ }
157
+ }
158
+ const test = Subject.new();
159
+ const values = { ref: "foobar" };
160
+ test.ref(values.ref);
161
+ expect(test.get()).toMatchObject(values);
162
+ });
163
+ it('will be accessible from a proxy', () => {
164
+ class Subject extends Model {
165
+ constructor() {
166
+ super(...arguments);
167
+ this.ref = ref();
168
+ }
169
+ }
170
+ const test = Subject.new();
171
+ test.get(state => {
172
+ expect(state.ref).not.toBeUndefined();
173
+ });
174
+ });
175
+ it.skip('will subscribe if current accessed', async () => {
176
+ class Subject extends Model {
177
+ constructor() {
178
+ super(...arguments);
179
+ this.ref = ref();
180
+ }
181
+ }
182
+ const test = Subject.new();
183
+ const effect = jest.fn(($) => {
184
+ void $.ref.current;
185
+ });
186
+ test.get(effect);
187
+ expect(effect).toBeCalledTimes(1);
188
+ test.ref("foobar");
189
+ await expect(test).toHaveUpdated();
190
+ expect(effect).toBeCalledTimes(2);
191
+ });
192
+ });
193
+ describe("proxy", () => {
194
+ class Subject extends Model {
195
+ constructor() {
196
+ super(...arguments);
197
+ this.foo = "foo";
198
+ this.bar = "bar";
199
+ this.refs = ref(this);
200
+ }
201
+ }
202
+ it("will match properties", () => {
203
+ const test = Subject.new();
204
+ for (const key in test)
205
+ expect(test.refs).toHaveProperty(key);
206
+ });
207
+ it("will match values via current", () => {
208
+ const test = Subject.new();
209
+ const { refs } = test;
210
+ for (const key in test) {
211
+ const value = test[key];
212
+ const { current } = refs[key];
213
+ expect(current).toBe(value);
214
+ }
215
+ });
216
+ it("will update values", async () => {
217
+ const test = Subject.new();
218
+ expect(test.foo).toBe("foo");
219
+ expect(test.bar).toBe("bar");
220
+ test.refs.foo("bar");
221
+ test.refs.bar("foo");
222
+ await expect(test).toHaveUpdated();
223
+ expect(test.foo).toBe("bar");
224
+ expect(test.bar).toBe("foo");
225
+ });
226
+ });
227
+ describe("mapped", () => {
228
+ const generateRef = jest.fn((key) => key);
229
+ class Test extends Model {
230
+ constructor() {
231
+ super(...arguments);
232
+ this.foo = "foo";
233
+ this.bar = "bar";
234
+ this.fields = ref(this, generateRef);
235
+ }
236
+ }
237
+ afterEach(() => {
238
+ generateRef.mockClear();
239
+ });
240
+ it("will run function for accessed keys", () => {
241
+ const { fields } = Test.new();
242
+ expect(fields.foo).toBe("foo");
243
+ expect(fields.bar).toBe("bar");
244
+ expect(generateRef).toBeCalledWith("foo");
245
+ expect(generateRef).toBeCalledWith("bar");
246
+ });
247
+ it("will run function only for accessed property", () => {
248
+ const { fields } = Test.new();
249
+ expect(fields.foo).toBe("foo");
250
+ expect(generateRef).toBeCalledWith("foo");
251
+ expect(generateRef).not.toBeCalledWith("bar");
252
+ });
253
+ it("will run function only once per property", () => {
254
+ const { fields } = Test.new();
255
+ expect(fields.foo).toBe("foo");
256
+ expect(fields.foo).toBe("foo");
257
+ expect(generateRef).toBeCalledTimes(1);
258
+ });
259
+ it("will throw if object is not this", () => {
260
+ class Test extends Model {
261
+ constructor() {
262
+ super(...arguments);
263
+ this.foo = "foo";
264
+ this.bar = "bar";
265
+ // @ts-expect-error
266
+ this.fields = ref({});
267
+ }
268
+ }
269
+ expect(() => Test.new()).toThrowError(`ref instruction does not support object which is not 'this'`);
270
+ });
271
+ it.skip("will not break on recursive", () => {
272
+ class Test extends Model {
273
+ constructor() {
274
+ super(...arguments);
275
+ this.foo = ref();
276
+ this.bar = ref(this);
277
+ }
278
+ }
279
+ Test.new();
280
+ });
281
+ });
@@ -0,0 +1,86 @@
1
+ import { context } from '../control';
2
+ import { event, update } from '../model';
3
+ import { use } from './use';
4
+ function set(value, argument) {
5
+ return use((key, subject) => {
6
+ const property = {};
7
+ if (typeof value == "function" || value instanceof Promise) {
8
+ function init() {
9
+ if (typeof value == "function")
10
+ try {
11
+ value = attempt(value.bind(subject, key));
12
+ }
13
+ catch (err) {
14
+ console.warn(`Generating initial value for ${subject}.${key} failed.`);
15
+ throw err;
16
+ }
17
+ property.get = argument === true;
18
+ if (value instanceof Promise) {
19
+ value
20
+ .then(value => {
21
+ update(subject, key, value);
22
+ return value;
23
+ })
24
+ .catch(err => {
25
+ event(subject, key);
26
+ property.get = () => { throw err; };
27
+ });
28
+ }
29
+ else
30
+ update(subject, key, value);
31
+ if (0 in arguments)
32
+ return subject[key];
33
+ }
34
+ if (argument)
35
+ init();
36
+ else
37
+ property.get = init;
38
+ }
39
+ else if (value !== undefined)
40
+ property.value = value;
41
+ if (typeof argument == "function") {
42
+ let unset;
43
+ property.set = function (value, previous) {
44
+ const ctx = context();
45
+ const out = argument.call(this, value, previous);
46
+ const flush = ctx();
47
+ if (out === false)
48
+ return out;
49
+ if (typeof unset == "function")
50
+ unset(value);
51
+ unset = (next) => {
52
+ if (typeof out == "function")
53
+ out(next);
54
+ flush();
55
+ };
56
+ };
57
+ }
58
+ else
59
+ property.set = value => {
60
+ property.get = undefined;
61
+ update(subject, key, value);
62
+ };
63
+ return property;
64
+ });
65
+ }
66
+ function attempt(fn) {
67
+ function retry(err) {
68
+ if (err instanceof Promise)
69
+ return err.then(compute);
70
+ else
71
+ throw err;
72
+ }
73
+ function compute() {
74
+ try {
75
+ const output = fn();
76
+ return output instanceof Promise
77
+ ? output.catch(retry)
78
+ : output;
79
+ }
80
+ catch (err) {
81
+ return retry(err);
82
+ }
83
+ }
84
+ return compute();
85
+ }
86
+ export { set };