import { Parser } from '@rekajs/parser';
import * as t from '@rekajs/types';
import { Reka } from '../reka';
let reka: Reka;
beforeEach(() => {
reka = Reka.create();
const program = Parser.parseProgram(`
component App() {
val count = 1;
} => (
{ count += 1; }} />
)
`);
reka.load(t.state({ program }));
});
const HistoryOperationTests = {
obj: {
update: () => {
const countStateLiteralInit = t.assert(
reka.program.components[0].state[0].init,
t.Literal
);
reka.change(() => {
countStateLiteralInit.value = 2;
});
return {
beforeChange: () => {
expect(countStateLiteralInit.value).toEqual(1);
},
afterChange: () => {
expect(countStateLiteralInit.value).toEqual(2);
},
};
},
add: () => {
const rootDivTagTplProps = t.assert(
reka.program.components[0].template,
t.TagTemplate
).props;
reka.change(() => {
rootDivTagTplProps['prop2'] = t.literal({ value: 'Bye' });
});
return {
beforeChange: () => {
expect(rootDivTagTplProps['prop2']).toBeUndefined();
},
afterChange: () => {
expect(rootDivTagTplProps['prop2']).not.toBeUndefined();
},
};
},
remove: () => {
const rootDivTagTplProps = t.assert(
reka.program.components[0].template,
t.TagTemplate
).props;
reka.change(() => {
delete rootDivTagTplProps['prop1'];
});
return {
beforeChange: () => {
expect(rootDivTagTplProps['prop1']).not.toBeUndefined();
},
afterChange: () => {
expect(rootDivTagTplProps['prop1']).toBeUndefined();
},
};
},
},
arrays: {
add: () => {
const component = t.assert(reka.program.components[0], t.RekaComponent);
reka.change(() => {
component.state.push(
t.val({
name: 'count2',
init: t.literal({ value: 0 }),
})
);
});
return {
beforeChange: () => {
expect(
component.state.find((state) => state.name === 'count2')
).toBeUndefined();
},
afterChange: () => {
expect(
component.state.find((state) => state.name === 'count2')
).not.toBeUndefined();
},
};
},
splice: () => {
const rootDivTagTpl = t.assert(
reka.program.components[0].template,
t.TagTemplate
);
reka.change(() => {
rootDivTagTpl.children.splice(0, 1); // remove text
rootDivTagTpl.children.splice(1, 0, t.tagTemplate({ tag: 'h1' })); // insert after button
});
expect(t.assert(rootDivTagTpl.children[0], t.TagTemplate).tag).toEqual(
'button'
);
expect(t.assert(rootDivTagTpl.children[1], t.TagTemplate).tag).toEqual(
'h1'
);
return {
beforeChange: () => {
expect(rootDivTagTpl.children.length).toEqual(2);
expect(
t.assert(rootDivTagTpl.children[0], t.TagTemplate).tag
).toEqual('text');
expect(
t.assert(rootDivTagTpl.children[1], t.TagTemplate).tag
).toEqual('button');
},
afterChange: () => {
expect(
t.assert(rootDivTagTpl.children[0], t.TagTemplate).tag
).toEqual('button');
expect(
t.assert(rootDivTagTpl.children[1], t.TagTemplate).tag
).toEqual('h1');
},
};
},
},
};
describe('History', () => {
for (const type in HistoryOperationTests) {
describe(`${type}`, () => {
for (const test in HistoryOperationTests[type]) {
it(`should be able to undo/redo ${test} op`, () => {
const fn = HistoryOperationTests[type][test];
const { beforeChange, afterChange } = fn();
// Verify mutation has been applied
afterChange();
// Verify undo has been applied
expect(reka.canUndo()).toEqual(true);
reka.undo();
beforeChange();
expect(reka.canUndo()).toEqual(false);
expect(reka.canRedo()).toEqual(true);
// Verify redo has been applied
reka.redo();
afterChange();
expect(reka.canRedo()).toEqual(false);
expect(reka.canUndo()).toEqual(true);
});
}
});
}
describe('rollback', () => {
it('should be able to rollback undo op if an error occurs', () => {
const divTpl = t.assert(
reka.program.components[0].template,
t.TagTemplate
);
const textTpl = t.assert(divTpl.children[0], t.TagTemplate);
const btnTpl = t.assert(divTpl.children[1], t.TagTemplate);
expect(t.assert(btnTpl.props.color, t.Literal).value).toEqual('red');
reka.change(() => {
btnTpl.props.color = t.literal({ value: 'blue' });
});
expect(t.assert(btnTpl.props.color, t.Literal).value).toEqual('blue');
reka.change(() => {
textTpl.props.value = t.literal({ value: 'Bye' });
});
reka.change(
() => {
// remove text tag template
divTpl.children.splice(0, 1);
},
{
history: {
ignore: true,
},
}
);
reka.undo();
expect(t.assert(btnTpl.props.color, t.Literal).value).toEqual('red');
});
});
describe('throttle', () => {
it('should add changes as a single stack item if changes are made within throttle threshold', async () => {
const divTplProp1 = t.assert(
reka.program.components[0].template,
t.TagTemplate,
(tpl) => t.assert(tpl.props.prop1, t.Literal)
);
reka.change(() => {
divTplProp1.value = 'hellos';
});
reka.change(
() => {
divTplProp1.value = 'helloss';
},
{
history: {
throttle: 100,
},
}
);
expect(divTplProp1.value).toEqual('helloss');
await new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 200);
});
reka.change(
() => {
divTplProp1.value = 'hellosss';
},
{
history: {
throttle: 100,
},
}
);
expect(divTplProp1.value).toEqual('hellosss');
reka.undo();
expect(divTplProp1.value).toEqual('helloss');
reka.undo();
expect(divTplProp1.value).toEqual('hello');
});
});
});