// @vitest-environment jsdom import { inspect } from 'util'; import { cleanup, render } from '@testing-library/react'; import React from 'react'; import { describe, expect, test } from 'vitest'; import { capture, manage, runInAction } from '../src'; import { auto } from '../src/react'; describe('multiple parent', () => { test('default', () => { const a = { b: { c: 0 } }; const ma = manage(a); const d = { b: ma.b }; const md = manage(d); d.b.c = 1; let [, writeLog] = runInAction(() => { d.b.c = 1; }); expect(inspect(writeLog)).toBe( `Map(1) { { c: 1 } => Map(1) { 'c' => 0 } }`, ); [, writeLog] = runInAction(() => { const [, readLog] = capture(() => { md.b.c = 2; }); expect(inspect(readLog)).toBe( `Map(1) { { b: { c: 2 } } => { get: Map(1) { 'b' => [Object] } } }`, ); }); expect(inspect(writeLog)).toBe( `Map(1) { { c: 2 } => Map(1) { 'c' => 0 } }`, ); [, writeLog] = runInAction(() => { a.b.c = 3; }); expect(inspect(writeLog)).toBe( `Map(1) { { c: 3 } => Map(1) { 'c' => 0 } }`, ); [, writeLog] = runInAction(() => { const [, readLog] = capture(() => { ma.b.c = 4; }); expect(inspect(readLog)).toBe( `Map(1) { { b: { c: 4 } } => { get: Map(1) { 'b' => [Object] } } }`, ); }); expect(inspect(writeLog)).toBe( `Map(1) { { c: 4 } => Map(1) { 'c' => 0 } }`, ); }); test('game', () => { let id = 0; class Bullet { public id: number; public speed = 10; public constructor() { this.id = id++; } } class Store { public bullets: { [key: number]: Bullet } = {}; } const store = manage(new Store()); store.bullets[0] = new Bullet(); const props = manage({ bullet: store.bullets[0] }); const [, readLog] = capture(() => { expect(props.bullet.speed).toBe(10); // trigger get }); expect(inspect(readLog)).toBe(`Map(2) { { bullet: Bullet { speed: 10, id: 0 } } => { get: Map(1) { 'bullet' => [Bullet] } }, Bullet { speed: 10, id: 0 } => { get: Map(1) { 'speed' => 10 } } }`); }); test('game with React', () => { let id = 0; class Bullet { public id: number; public speed = 10; public constructor() { this.id = id++; } } class Store { public bullets: { [key: number]: Bullet } = {}; } const events: string[] = []; const BulletComponent = auto((props: { bullet: Bullet }) => { events.push('BulletComponent start'); const { bullet } = props; events.push('BulletComponent end'); return bullet.speed; }); const App = auto((props: { store: Store }) => { events.push('App start'); const { store } = props; const temp = Object.values(store.bullets).map((bullet) => ( )); events.push('App end'); return temp; }); const store = manage(new Store()); store.bullets[0] = new Bullet(); store.bullets[1] = new Bullet(); render(); expect(events).toEqual([ 'App start', 'App end', 'BulletComponent start', 'BulletComponent end', 'BulletComponent start', 'BulletComponent end', ]); cleanup(); // this test case proves that React is "async" }); test('plain React', () => { let id = 0; class Bullet { public id: number; public speed = 10; public constructor() { this.id = id++; } } class Store { public bullets: { [key: number]: Bullet } = {}; } const events: string[] = []; const BulletComponent = (props: { bullet: Bullet }) => { events.push('BulletComponent start'); const { bullet } = props; events.push('BulletComponent end'); return bullet.speed; }; const App = (props: { store: Store }) => { events.push('App start'); const { store } = props; const temp = Object.values(store.bullets).map((bullet) => ( )); events.push('App end'); return temp; }; const store = manage(new Store()); store.bullets[0] = new Bullet(); store.bullets[1] = new Bullet(); render(); expect(events).toEqual([ 'App start', 'App end', 'BulletComponent start', 'BulletComponent end', 'BulletComponent start', 'BulletComponent end', ]); cleanup(); // this test case proves that React is "async" }); });