Skip to content

Commit 1d4e692

Browse files
authored
Add ViewModel property type test coverage (#273)
Adds 15 new harness tests covering listener callback invocation, set() + getValueAsync() round-trips, and removeListeners() for all ViewModel property types. iOS native coverage goes from 41% to 50%. Target types: BooleanProperty (16→84%), ColorProperty (14→84%), EnumProperty (16→84%), TriggerProperty (0→81%), StringProperty (37→84%), NumberProperty (85→90%).
1 parent 0537203 commit 1d4e692

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

example/__tests__/viewmodel-properties.harness.ts

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { RiveFileFactory } from '@rive-app/react-native';
55

66
const DATABINDING = require('../assets/rive/databinding.riv');
77

8+
function isExperimental() {
9+
return RiveFileFactory.getBackend() === 'experimental';
10+
}
11+
812
function expectDefined<T>(value: T): asserts value is NonNullable<T> {
913
expect(value).toBeDefined();
1014
}
@@ -250,3 +254,182 @@ describe('Property Listeners', () => {
250254
expect(() => cleanup2()).not.toThrow();
251255
});
252256
});
257+
258+
const delay = (ms: number) => new Promise((r) => setTimeout(r, ms));
259+
260+
describe('Listener callback invocation (experimental only)', () => {
261+
// The experimental backend emits the current value on addListener;
262+
// legacy only fires on subsequent changes — these tests would hang.
263+
it('numberProperty listener emits current value', async () => {
264+
if (!isExperimental()) return;
265+
const instance = await createGordonInstance();
266+
const prop = instance.numberProperty('age');
267+
expectDefined(prop);
268+
const value = await new Promise<number>((resolve) => {
269+
const cleanup = prop.addListener((v) => {
270+
cleanup();
271+
resolve(v);
272+
});
273+
});
274+
expect(value).toBe(30);
275+
});
276+
277+
it('stringProperty listener emits current value', async () => {
278+
if (!isExperimental()) return;
279+
const instance = await createGordonInstance();
280+
const prop = instance.stringProperty('name');
281+
expectDefined(prop);
282+
const value = await new Promise<string>((resolve) => {
283+
const cleanup = prop.addListener((v) => {
284+
cleanup();
285+
resolve(v);
286+
});
287+
});
288+
expect(value).toBe('Gordon');
289+
});
290+
291+
it('booleanProperty listener emits current value', async () => {
292+
if (!isExperimental()) return;
293+
const instance = await createGordonInstance();
294+
const prop = instance.booleanProperty('likes_popcorn');
295+
expectDefined(prop);
296+
const value = await new Promise<boolean>((resolve) => {
297+
const cleanup = prop.addListener((v) => {
298+
cleanup();
299+
resolve(v);
300+
});
301+
});
302+
expect(value).toBe(false);
303+
});
304+
305+
it('colorProperty listener emits current value', async () => {
306+
if (!isExperimental()) return;
307+
const instance = await createGordonInstance();
308+
const prop = instance.colorProperty('favourite_color');
309+
expectDefined(prop);
310+
const value = await new Promise<number>((resolve) => {
311+
const cleanup = prop.addListener((v) => {
312+
cleanup();
313+
resolve(v);
314+
});
315+
});
316+
const rgb = getRGB(value);
317+
expect(rgb).toEqual({ r: 255, g: 0, b: 0 });
318+
});
319+
320+
it('enumProperty listener emits current value', async () => {
321+
if (!isExperimental()) return;
322+
const instance = await createGordonInstance();
323+
const prop = instance.enumProperty('favourite_pet');
324+
expectDefined(prop);
325+
const value = await new Promise<string>((resolve) => {
326+
const cleanup = prop.addListener((v) => {
327+
cleanup();
328+
resolve(v);
329+
});
330+
});
331+
expect(value).toBe('dog');
332+
});
333+
});
334+
335+
describe('set() method works for all property types', () => {
336+
it('numberProperty set() updates value', async () => {
337+
const instance = await createGordonInstance();
338+
const prop = instance.numberProperty('age');
339+
expectDefined(prop);
340+
prop.set(99);
341+
await delay(100);
342+
expect(await prop.getValueAsync()).toBe(99);
343+
});
344+
345+
it('stringProperty set() updates value', async () => {
346+
const instance = await createGordonInstance();
347+
const prop = instance.stringProperty('name');
348+
expectDefined(prop);
349+
prop.set('Alice');
350+
await delay(100);
351+
expect(await prop.getValueAsync()).toBe('Alice');
352+
});
353+
354+
it('booleanProperty set() updates value', async () => {
355+
const instance = await createGordonInstance();
356+
const prop = instance.booleanProperty('likes_popcorn');
357+
expectDefined(prop);
358+
prop.set(true);
359+
await delay(100);
360+
expect(await prop.getValueAsync()).toBe(true);
361+
});
362+
363+
it('colorProperty set() updates value', async () => {
364+
const instance = await createGordonInstance();
365+
const prop = instance.colorProperty('favourite_color');
366+
expectDefined(prop);
367+
prop.set(0xff00ff00);
368+
await delay(100);
369+
const rgb = getRGB(await prop.getValueAsync());
370+
expect(rgb).toEqual({ r: 0, g: 255, b: 0 });
371+
});
372+
373+
it('enumProperty set() updates value', async () => {
374+
const instance = await createGordonInstance();
375+
const prop = instance.enumProperty('favourite_pet');
376+
expectDefined(prop);
377+
prop.set('cat');
378+
await delay(100);
379+
expect(await prop.getValueAsync()).toBe('cat');
380+
});
381+
382+
it('triggerProperty trigger() does not throw', async () => {
383+
const instance = await createGordonInstance();
384+
const prop = instance.triggerProperty('jump');
385+
expectDefined(prop);
386+
prop.trigger();
387+
await delay(100);
388+
});
389+
});
390+
391+
describe('set() + getValueAsync() round-trip', () => {
392+
it('booleanProperty set + getValueAsync', async () => {
393+
const instance = await createGordonInstance();
394+
const prop = instance.booleanProperty('likes_popcorn');
395+
expectDefined(prop);
396+
prop.set(true);
397+
await delay(100);
398+
expect(await prop.getValueAsync()).toBe(true);
399+
});
400+
401+
it('colorProperty set + getValueAsync', async () => {
402+
const instance = await createGordonInstance();
403+
const prop = instance.colorProperty('favourite_color');
404+
expectDefined(prop);
405+
prop.set(0xff0000ff);
406+
await delay(100);
407+
const rgb = getRGB(await prop.getValueAsync());
408+
expect(rgb).toEqual({ r: 0, g: 0, b: 255 });
409+
});
410+
411+
it('enumProperty set + getValueAsync', async () => {
412+
const instance = await createGordonInstance();
413+
const prop = instance.enumProperty('favourite_pet');
414+
expectDefined(prop);
415+
prop.set('cat');
416+
await delay(100);
417+
expect(await prop.getValueAsync()).toBe('cat');
418+
});
419+
});
420+
421+
describe('removeListeners stops callbacks', () => {
422+
it('no callbacks fire after removeListeners', async () => {
423+
const instance = await createGordonInstance();
424+
const prop = instance.numberProperty('age');
425+
expectDefined(prop);
426+
const values: number[] = [];
427+
prop.addListener((v) => values.push(v));
428+
await delay(200);
429+
const countBefore = values.length;
430+
prop.removeListeners();
431+
prop.set(999);
432+
await delay(300);
433+
expect(values.length).toBe(countBefore);
434+
});
435+
});

0 commit comments

Comments
 (0)