/** * 响应式 rstate 测试文件 */ import { XStore, XType, XString } from '../pastore'; import { delay } from './helpers'; interface SimpleState extends XType { name: string, age: number, isMale: boolean, teacher: { name: string, age: number, isMale: boolean }, deepNested: { level2: { value: string, level3: { value: string } } }, array_plain: Array, array_object: Array<{ name: string, age: number, isMale: boolean }>, array_object_empty: Array<{ name: string, age: number, isMale: boolean }>, array_array: Array> } class SimpleStore extends XStore{ } let initState: SimpleState = { name: 'Peter', age: 10, isMale: true, teacher: { name: 'Peter', age: 10, isMale: true, }, deepNested: { level2: { value: 'l2', level3: { value: 'l3' } } }, array_plain: ['a0', 'a1'], array_object: [{ name: 'n0', age: 10, isMale: true }, { name: 'n1', age: 20, isMale: false }], array_object_empty: [], array_array: [ ['a00', 'a01'], ['a10', 'a11'] ] } let store = new SimpleStore(initState); describe('responsive state test suit', function () { describe('state init', function () { describe('root prop', function () { describe('get', function () { it('string', function () { expect(store.state.name).toEqual(store.imState.name) }) it('number', function () { expect(store.imState.age).toEqual(store.state.age) }) it('boolean', function () { expect(store.imState.isMale).toEqual(store.state.isMale) }) }) describe('set', function () { it('string', async function () { store.state.name = 'Tom' await delay(0) expect(store.imState.name).toEqual('Tom') expect(store.state.name).toEqual(store.imState.name) }) it('number', async function () { store.state.age = 20 await delay(0) expect(store.imState.age).toEqual(20) expect(store.state.age).toEqual(20) }) it('boolean', async function () { store.state.isMale = false await delay(0) expect(store.imState.isMale).toEqual(false) expect(store.state.isMale).toEqual(false) }) it('async', async function () { store.state.isMale = true expect(store.imState.isMale).toEqual(false) expect(store.state.isMale).toEqual(false) await delay(0) expect(store.imState.isMale).toEqual(true) expect(store.state.isMale).toEqual(true) }) }) }) describe('nested prop', function () { describe('object value', function () { describe('get', function () { it('string', function () { expect(store.state.teacher.name).toEqual(store.imState.teacher.name) }) it('number', function () { expect(store.imState.teacher.age).toEqual(store.state.teacher.age) }) it('boolean', function () { expect(store.imState.teacher.isMale).toEqual(store.state.teacher.isMale) }) }) describe('set', function () { it('string', async function () { store.state.teacher.name = 'Tom' await delay(0) expect(store.imState.teacher.name).toEqual('Tom') expect(store.state.teacher.name).toEqual(store.imState.teacher.name) }) it('number', async function () { store.state.teacher.age = 20 await delay(0) expect(store.imState.teacher.age).toEqual(20) expect(store.state.teacher.age).toEqual(20) }) it('boolean', async function () { store.state.teacher.isMale = false await delay(0) expect(store.imState.teacher.isMale).toEqual(false) expect(store.state.teacher.isMale).toEqual(false) }) it('set total new object', async function () { let newTeacher = { name: 'new', age: 30, isMale: true } store.state.teacher = newTeacher await delay(0) expect(store.imState.teacher).toEqual(newTeacher) expect(store.state.teacher).toEqual(newTeacher) }) }) describe('deep nested', function () { it('level2', async function () { expect(store.state.deepNested.level2.value).toEqual(store.imState.deepNested.level2.value) store.state.deepNested.level2.value += '!' await delay(0) expect(store.imState.deepNested.level2.value).toEqual('l2!') expect(store.state.deepNested.level2.value).toEqual('l2!') }) it('level3', async function () { expect(store.state.deepNested.level2.level3.value).toEqual(store.imState.deepNested.level2.level3.value) store.state.deepNested.level2.level3.value += '!' await delay(0) expect(store.imState.deepNested.level2.level3.value).toEqual('l3!') expect(store.state.deepNested.level2.level3.value).toEqual('l3!') }) }) }) describe('array value', function () { it('plain elements', async function () { expect(store.state.array_plain).toEqual(store.imState.array_plain) store.state.array_plain[0] = 'new0' store.state.array_plain[1] = 'new1' await delay(0) expect(store.imState.array_plain).toEqual(['new0', 'new1']) expect(store.state.array_plain).toEqual(['new0', 'new1']) }) it('object elements', async function () { expect(store.state.array_object).toEqual(store.imState.array_object) let newObject0 = { name: 'new1', age: 40, isMale: true } let newObject1 = { name: 'new2', age: 50, isMale: false } store.state.array_object[0].name = newObject0.name; store.state.array_object[0].age = newObject0.age; store.state.array_object[0].isMale = newObject0.isMale; store.state.array_object[1] = newObject1; await delay(0) expect(store.imState.array_object).toEqual([newObject0, newObject1]) expect(store.state.array_object).toEqual([newObject0, newObject1]) }) it('array elements (multi-dimensions array)', async function () { expect(store.state.array_array).toEqual(store.imState.array_array) store.state.array_array[0][0] = 'new00'; store.state.array_array[0][1] = 'new01'; store.state.array_array[1] = ['new10', 'new11']; await delay(0) expect(store.imState.array_array).toEqual([['new00', 'new01'], ['new10', 'new11']]) expect(store.state.array_array).toEqual([['new00', 'new01'], ['new10', 'new11']]) }) }) }) }) describe('state change', function () { describe('array mutate method', function () { describe('push', function () { it('first push', async function () { // 插入新元素 expect(store.imState.array_object.length).toEqual(2) let newElement = { name: 'n2', age: 40, isMale: false } store.state.array_object.push(newElement) await delay(0) expect(store.imState.array_object.length).toEqual(3) expect(store.state.array_object.length).toEqual(3) expect(store.imState.array_object[2]).toEqual(newElement) expect(store.state.array_object[2]).toEqual(newElement) // 新元素的操作 store.state.array_object[2].name = 'n2!' await delay(0) expect(store.imState.array_object[2].name).toEqual('n2!') }) it('next push in different async batch', async function () { let newElement = { name: 'n3', age: 50, isMale: true } store.state.array_object.push(newElement) await delay(0) store.state.array_object[3].age = 100 await delay(0) expect(store.imState.array_object.length).toEqual(4) expect(store.state.array_object.length).toEqual(4) expect(store.imState.array_object[3].age).toEqual(100) expect(store.state.array_object[3].age).toEqual(100) }) it('next push in the same async batch', async function () { let newElement0 = { name: 'new-0', age: 50, isMale: true } let newElement1 = { name: 'new-1', age: 50, isMale: true } // 如果 push 是按引用传递,这里有个问题要注意,如果两个都是压入相同的引用element, 会导致无法压入第二个 // 遇到问题要考虑这一点:按值传递和按引用传递的控制,本框架的修改会统一用按值传递的模式实现(如果发现按引用传递则需要修改) store.state.array_object.push(newElement0) expect(store.state.array_object.length).toEqual(5) store.state.array_object.push(newElement1) expect(store.state.array_object.length).toEqual(6) await delay(0) expect(store.imState.array_object.length).toEqual(6) expect(store.imState.array_object[4]).toEqual(newElement0) expect(store.state.array_object[4]).toEqual(newElement0) expect(store.imState.array_object[5]).toEqual(newElement1) expect(store.state.array_object[5]).toEqual(newElement1) }) it('push into an init empty array', async function(){ let newElement = { name: 'newElement', age: 10, isMale: true } expect(store.state.array_object_empty.length).toEqual(0) store.state.array_object_empty.push(newElement); await delay(0) expect(store.state.array_object_empty).toEqual([newElement]) expect(store.state.array_object_empty.length).toEqual(1) store.state.array_object_empty = [] await delay(0) }) }) describe('pop', function () { // 注意,此时通过 store.state.array_object[3] 获取的对象,已经不可以访问了,因为数据里没有第3个元素了 it('first pop', async function () { let lastValue_pop = store.state.array_object.pop() await delay(0) expect(lastValue_pop).toEqual({ name: 'new-1', age: 50, isMale: true }) expect(store.imState.array_object[5]).toEqual(undefined) expect(store.imState.array_object.length).toEqual(5) expect(store.state.array_object.length).toEqual(5) }) // 异步间连续操作 it('next pop in different async batch', async function () { let lastValue_pop = store.state.array_object.pop() expect(store.state.array_object.length).toEqual(4) await delay(0) expect(store.imState.array_object.length).toEqual(4) expect(lastValue_pop).toEqual({ name: 'new-0', age: 50, isMale: true }) expect(store.imState.array_object[4]).toEqual(undefined) }) // 异步内连续操作 it('next pop in the same async batch', async function () { let value_3 = store.imState.array_object[3] let value_2 = store.imState.array_object[2] let lastValue_pop0 = store.state.array_object.pop() let lastValue_pop1 = store.state.array_object.pop() await delay(0) expect(store.imState.array_object.length).toBe(2) expect(lastValue_pop0).toEqual(value_3) expect(lastValue_pop1).toEqual(value_2) }) }) describe('unshift', function () { it('first unshift', async function () { expect(store.imState.array_object.length).toBe(2) let newValue = { name: 'unshift-0', age: 50, isMale: true } let oldValue_0 = store.imState.array_object[0] let oldValue_1 = store.imState.array_object[1] store.state.array_object.unshift(newValue) expect(store.state.array_object.length).toBe(3) expect(store.imState.array_object.length).toBe(3) expect(store.state.array_object[0]).toEqual(newValue) expect(store.state.array_object[1]).toEqual(oldValue_0) expect(store.state.array_object[2]).toEqual(oldValue_1) }) it('next unshift in different async batch', async function () { let newValue_0 = { name: 'unshift-1', age: 50, isMale: true } let newValue_1 = { name: 'unshift-2', age: 50, isMale: true } let oldArray = store.imState.array_object; store.state.array_object.unshift(newValue_0) await delay(0) store.state.array_object.unshift(newValue_1) await delay(0) expect(store.state.array_object.length).toEqual(5) expect(store.state.array_object).toEqual([newValue_1, newValue_0, ...oldArray]) }) it('next unshift in the same async batch', async function () { let newValue_0 = { name: 'unshift-3', age: 50, isMale: true } let newValue_1 = { name: 'unshift-4', age: 50, isMale: true } let oldArray = store.imState.array_object store.state.array_object.unshift(newValue_0) store.state.array_object.unshift(newValue_1) await delay(0) expect(store.state.array_object.length).toEqual(7) expect(store.state.array_object).toEqual([newValue_1, newValue_0, ...oldArray]) }) it('unshift into an init empty array', async function(){ let newElement = { name: 'newElement', age: 10, isMale: true } expect(store.state.array_object_empty.length).toEqual(0) store.state.array_object_empty.push(newElement); await delay(0) expect(store.state.array_object_empty).toEqual([newElement]) expect(store.state.array_object_empty.length).toEqual(1) store.state.array_object_empty = [] await delay(0) }) }) describe('shift', async function () { it('first shift', async function () { let oldArray = store.imState.array_object let shiftedObject = store.state.array_object.shift() expect(store.imState.array_object.length).toBe(6) expect(store.state.array_object.length).toBe(6) expect(store.state.array_object).toEqual(oldArray.slice(1)) expect(shiftedObject).toEqual(oldArray[0]) }) it('next shift in different async batch', async function () { let oldArray = store.imState.array_object let shiftedObject_0 = store.state.array_object.shift() await delay(0) let shiftedObject_1 = store.state.array_object.shift() await delay(0) expect(store.state.array_object).toEqual(oldArray.slice(2)) expect(shiftedObject_0).toEqual(oldArray[0]) expect(shiftedObject_1).toEqual(oldArray[1]) }) it('next shift in the same async batch', async function () { let oldArray = store.imState.array_object let shiftedObject_0 = store.state.array_object.shift() let shiftedObject_1 = store.state.array_object.shift() await delay(0) expect(store.state.array_object).toEqual(oldArray.slice(2)) expect(shiftedObject_0).toEqual(oldArray[0]) expect(shiftedObject_1).toEqual(oldArray[1]) // 注意, 连续 pop + push 混用会导致 pop 出来的元素可能出错(目前这两者存在依赖) // 但是 目标数组的处理结果时正确的 }) }) describe('splice', function () { it('can splice', async function () { // delete and add let newValue_0 = { name: 'splice-0', age: 50, isMale: true } let newValue_1 = { name: 'splice-1', age: 50, isMale: true } let newValue_2 = { name: 'splice-2', age: 50, isMale: true } let newValue_3 = { name: 'splice-3', age: 50, isMale: true } let oldArray = store.imState.array_object; store.state.array_object.push(newValue_0) store.state.array_object.push(newValue_1) store.state.array_object.push(newValue_2) await delay(0) let poppedElements = store.state.array_object.splice(2, 2, newValue_3) await delay(0) expect(store.state.array_object.length).toEqual(4) expect(store.imState.array_object.length).toEqual(4) expect(poppedElements).toEqual([newValue_0, newValue_1]) expect(store.state.array_object[2]).toEqual(newValue_3) expect(store.state.array_object[3]).toEqual(newValue_2) expect(store.imState.array_object[2]).toEqual(newValue_3) expect(store.imState.array_object[3]).toEqual(newValue_2) expect((store.imState.array_object[3].age as XType).__xpath__).toEqual('.array_object.3.age') // delete one poppedElements = store.state.array_object.splice(2, 1) await delay(0) expect(store.state.array_object.length).toEqual(3) expect(store.imState.array_object.length).toEqual(3) expect(poppedElements).toEqual([newValue_3]) expect(store.state.array_object[2]).toEqual(newValue_2) expect(store.imState.array_object[2]).toEqual(newValue_2) expect((store.imState.array_object[2].age as XType).__xpath__).toEqual('.array_object.2.age') // add one store.state.array_object.splice(2, 0, newValue_3) await delay(0) expect(store.state.array_object.length).toEqual(4) // expect(store.imState.array_object.length).toEqual(4) // expect(store.state.array_object[2]).toEqual(newValue_3) // expect(store.state.array_object[3]).toEqual(newValue_2) // expect(store.imState.array_object[2]).toEqual(newValue_3) // expect(store.imState.array_object[3]).toEqual(newValue_2) // expect((store.imState.array_object[3].age as XType).__xpath__).toEqual('.array_object.3.age') }) }) describe('sort', function () { it('can sort', async function () { store.state.array_object[1].age = 2; store.state.array_object[2].age = 1; store.state.array_object[0].age = 3; store.state.array_object[3].age = 0; await delay(0) store.state.array_object.sort((a, b) => a.age - b.age) await delay(0) expect(store.state.array_object.map(v => v.age)).toEqual([0, 1, 2, 3]) }) }) describe('reverse', async function () { it('can reverse', async function () { store.state.array_object.reverse() await delay(0) expect(store.state.array_object.map(v => v.age)).toEqual([3, 2, 1, 0]) }) }) }) }) describe('null situation', function () { // NOTE: 虽然支持设为null值,但是一般不建议用null值 describe('plain node', function () { it('string', async function(){ // to be null (store.state.teacher.name as any) = null; await delay(0); expect(store.imState.teacher.name).toEqual(null); expect(store.state.teacher.name).toEqual(null); // to be not null store.state.teacher.name = 'new teacher'; await delay(0); expect(store.imState.teacher.name).toEqual('new teacher'); expect(store.state.teacher.name).toEqual('new teacher'); }) it('number', async function(){ // to be null (store.state.teacher.age as any) = null; await delay(0); expect(store.imState.teacher.age).toEqual(null); expect(store.state.teacher.age).toEqual(null); // to be not null store.state.teacher.age = 100; await delay(0); expect(store.imState.teacher.age).toEqual(100); expect(store.state.teacher.age).toEqual(100); }) it('boolean', async function(){ // to be null (store.state.teacher.isMale as any) = null; await delay(0); expect(store.imState.teacher.isMale).toEqual(null); expect(store.state.teacher.isMale).toEqual(null); // to be not null store.state.teacher.isMale = true; await delay(0); expect(store.imState.teacher.isMale).toEqual(true); expect(store.state.teacher.isMale).toEqual(true); }) }) describe('object node', function () { it('make an object to be null', async function () { (store.state.teacher as any) = null; await delay(0); expect(store.imState.teacher).toEqual(null); expect(store.state.teacher).toEqual(null); }) it('make null to be an object', async function () { // 注意,新对象的结构要一致(正常业务逻辑下是一致的) let newValue = { name: 'Boy', age: 20, isMale: true } store.state.teacher = newValue await delay(0); expect(store.imState.teacher).toEqual(newValue); expect(store.state.teacher).toEqual(newValue); store.state.teacher.name = 'Good' await delay(0); expect(store.imState.teacher.name).toEqual('Good'); }) }) describe('array node', function () { it('make an array to be empty array', async function () { store.state.array_object = []; await delay(0); expect(store.imState.array_object).toEqual([]); expect(store.state.array_object).toEqual([]); }) it('make middle empty array to return same stucture array', async function () { // 注意,目前用方法二实现,新数组的结构不一定要要一致(正常业务逻辑下是一致的) // 如果用方法一实现则需要,方法一性能会高一些 let newElement = { name: 'not empty array', age: 20, isMale: true } let newArray = [newElement, newElement, newElement] store.state.array_object = newArray await delay(0); expect(store.imState.array_object).toEqual(newArray); expect(store.state.array_object).toEqual(newArray); store.state.array_object[2].name = 'new value' await delay(0); expect(store.imState.array_object[2].name).toEqual('new value'); expect(store.state.array_object[2].name).toEqual('new value'); }) it('make initial empty array to be not empty array', async function () { let newElement = { name: 'not empty array', age: 20, isMale: true } let newArray = [newElement, newElement, newElement] store.state.array_object_empty = newArray await delay(0); expect(store.imState.array_object_empty).toEqual(newArray); expect(store.state.array_object_empty).toEqual(newArray); store.state.array_object_empty[2].name = 'new value' await delay(0); expect(store.imState.array_object_empty[2].name).toEqual('new value'); expect(store.state.array_object_empty[2].name).toEqual('new value'); }) it('make array to be another array', async function () { // 注意,目前用方法二实现,新数组的结构不一定要要一致(正常业务逻辑下是一致的) // 如果用方法一实现则需要,方法一性能会高一些 let newElement = { name: 'another array', age: 21, isMale: false } let newArray = [newElement, newElement] store.state.array_object = newArray await delay(0); expect(store.imState.array_object).toEqual(newArray); expect(store.state.array_object).toEqual(newArray); store.state.array_object[1].name = 'new value' await delay(0); expect(store.imState.array_object[1].name).toEqual('new value'); expect(store.state.array_object[1].name).toEqual('new value'); }) it('array and null switching', async function () { // 支持设 array 为 null 但是不推荐,请使用 [] 代替 // to be null (store.state.array_object as any) = null await delay(0); expect(store.imState.array_object).toEqual(null); expect(store.state.array_object).toEqual(null); // to be array let newElement = { name: 'another array', age: 21, isMale: false } let newArray = [newElement, newElement] store.state.array_object = newArray await delay(0); expect(store.imState.array_object).toEqual(newArray); expect(store.state.array_object).toEqual(newArray); store.state.array_object[1].name = 'new value' await delay(0); expect(store.imState.array_object[1].name).toEqual('new value'); expect(store.state.array_object[1].name).toEqual('new value'); }) }) }) describe('get rstate', function(){ it('root', function(){ expect(store.getResponsiveState(store.imState)).toBe(store.state) }) it('nest', function(){ expect(store.getResponsiveState(store.imState.age as XType)).toBe(store.state.age) expect(store.getResponsiveState(store.imState.teacher as XType)).toBe(store.state.teacher) expect(store.getResponsiveState(store.imState.teacher.name as XType)).toBe(store.state.teacher.name) }) }) })