Skip to content
This repository was archived by the owner on Oct 16, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion examples/react/functional-component-ts/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './App.css';
import { useAgile, useWatcher } from '@agile-ts/react';
import { useEvent } from '@agile-ts/event';
import {
COUNTUP,
MY_COLLECTION,
MY_COMPUTED,
MY_EVENT,
Expand All @@ -13,6 +14,7 @@ import {
import { globalBind } from '@agile-ts/core';

let rerenderCount = 0;
let rerenderCountInCountupView = 0;

const App = (props: any) => {
// Note: Rerenders twice because of React Strickt Mode (also useState does trigger a rerender twice)
Expand Down Expand Up @@ -56,6 +58,19 @@ const App = (props: any) => {
globalBind('__core__', { ...require('./core') });
}, []);

const CountupView = () => {
const countup = useAgile(COUNTUP);
rerenderCountInCountupView++;
return (
<div style={{ backgroundColor: 'white', padding: 10 }}>
<p style={{ color: 'black' }}>Countup: {countup}</p>
<p style={{ color: 'black' }}>
Rerender Count of count up View: {rerenderCountInCountupView}
</p>
</div>
);
};

return (
<div className="App">
<header className="App-header">
Expand Down Expand Up @@ -124,7 +139,8 @@ const App = (props: any) => {
}>
Update mySelector value
</button>
<p>{rerenderCount}</p>
<p>Rerender Count: {rerenderCount}</p>
<CountupView />
</header>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions examples/react/functional-component-ts/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ App.registerStorage(
})
);

export const COUNTUP = App.createState(1).interval((value) => value + 1, 1000);
export const MY_STATE = App.createState<string>('MyState', { key: 'my-state' }); //.persist();
export const MY_STATE_2 = App.createState<string>('MyState2', {
key: 'my-state2',
Expand Down
64 changes: 56 additions & 8 deletions packages/core/src/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export class State<ValueType = any> {

public watchers: { [key: string]: StateWatcherCallback<ValueType> } = {};

public currentInterval?: NodeJS.Timer | number;

/**
* @public
* State - Class that holds one Value and causes rerender on subscribed Components
Expand Down Expand Up @@ -402,10 +404,12 @@ export class State<ValueType = any> {
defaultStorageKey: null,
});

if (this.persistent)
if (this.persistent) {
Agile.logger.warn(
`By persisting the State '${this._key}' twice you overwrite the old Persistent Instance!`
`By persisting the State '${this._key}' twice you overwrite the old Persistent Instance!`,
this.persistent
);
}

// Create persistent -> Persist Value
this.persistent = new StatePersistent<ValueType>(this, {
Expand All @@ -428,19 +432,63 @@ export class State<ValueType = any> {
* @param callback - Callback Function
*/
public onLoad(callback: (success: boolean) => void): this {
if (this.persistent) {
this.persistent.onLoad = callback;

// If State is already 'isPersisted' the loading was successful -> callback can be called
if (this.isPersisted) callback(true);
} else {
if (!this.persistent) {
Agile.logger.error(
`Please make sure you persist the State '${this._key}' before using the 'onLoad' function!`
);
return this;
}

this.persistent.onLoad = callback;

// If State is already 'isPersisted' the loading was successful -> callback can be called
if (this.isPersisted) callback(true);

return this;
}

//=========================================================================================================
// Interval
//=========================================================================================================
/**
* @public
* Calls callback at certain intervals in milliseconds and assigns the callback return value to the State
* @param callback- Callback that is called on each interval and should return the new State value
* @param ms - The intervals in milliseconds
*/
public interval(
callback: (value: ValueType) => ValueType,
ms?: number
): this {
if (this.currentInterval) {
Agile.logger.warn(
`You can only have one interval active!`,
this.currentInterval
);
return this;
}

this.currentInterval = setInterval(() => {
this.set(callback(this._value));
}, ms ?? 1000);

return this;
}

//=========================================================================================================
// Clear Interval
//=========================================================================================================
/**
* @public
* Clears the current Interval
*/
public clearInterval(): void {
if (this.currentInterval) {
clearInterval(this.currentInterval as number);
delete this.currentInterval;
}
}

//=========================================================================================================
// Copy
//=========================================================================================================
Expand Down
1 change: 0 additions & 1 deletion packages/core/tests/unit/collection/selector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe('Selector Tests', () => {
dummyCollection = new Collection<ItemInterface>(dummyAgile);

jest.spyOn(Selector.prototype, 'select');
console.warn = jest.fn();
});

it('should create Selector and call initial select (default config)', () => {
Expand Down
112 changes: 110 additions & 2 deletions packages/core/tests/unit/state/state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,8 @@ describe('State Tests', () => {
});

it('should overwrite existing persistent with a warning', () => {
numberState.persistent = new StatePersistent(numberState);
const oldPersistent = new StatePersistent(numberState);
numberState.persistent = oldPersistent;

numberState.persist('newPersistentKey');

Expand All @@ -656,7 +657,8 @@ describe('State Tests', () => {
defaultStorageKey: null,
});
expect(console.warn).toHaveBeenCalledWith(
`Agile Warn: By persisting the State '${numberState._key}' twice you overwrite the old Persistent Instance!`
`Agile Warn: By persisting the State '${numberState._key}' twice you overwrite the old Persistent Instance!`,
oldPersistent
);
});
});
Expand Down Expand Up @@ -694,6 +696,112 @@ describe('State Tests', () => {
});
});

describe('interval function tests', () => {
const dummyCallbackFunction = jest.fn();
const dummyCallbackFunction2 = jest.fn();

beforeEach(() => {
jest.useFakeTimers();
numberState.set = jest.fn();
});

afterEach(() => {
jest.clearAllTimers();
});

it('should create an interval (without custom milliseconds)', () => {
dummyCallbackFunction.mockReturnValueOnce(10);

numberState.interval(dummyCallbackFunction);

jest.runTimersToTime(1000); // travel 1000s in time -> execute interval

expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenLastCalledWith(
expect.any(Function),
1000
);
expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value);
expect(numberState.set).toHaveBeenCalledWith(10);
expect(numberState.currentInterval).toEqual({
id: expect.anything(),
ref: expect.anything(),
unref: expect.anything(),
});
expect(console.warn).not.toHaveBeenCalled();
});

it('should create an interval (with custom milliseconds)', () => {
dummyCallbackFunction.mockReturnValueOnce(10);

numberState.interval(dummyCallbackFunction, 2000);

jest.runTimersToTime(2000); // travel 2000 in time -> execute interval

expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenLastCalledWith(
expect.any(Function),
2000
);
expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value);
expect(numberState.set).toHaveBeenCalledWith(10);
expect(numberState.currentInterval).toEqual({
id: expect.anything(),
ref: expect.anything(),
unref: expect.anything(),
});
expect(console.warn).not.toHaveBeenCalled();
});

it("shouldn't be able to create second interval and print warning", () => {
numberState.interval(dummyCallbackFunction, 3000);
const currentInterval = numberState.currentInterval;
numberState.interval(dummyCallbackFunction2);

expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenLastCalledWith(
expect.any(Function),
3000
);
expect(numberState.currentInterval).toStrictEqual(currentInterval);
expect(console.warn).toHaveBeenCalledWith(
'Agile Warn: You can only have one interval active!',
numberState.currentInterval
);
});
});

describe('clearInterval function tests', () => {
const dummyCallbackFunction = jest.fn();

beforeEach(() => {
jest.useFakeTimers();
numberState.set = jest.fn();
});

afterEach(() => {
jest.clearAllTimers();
});

it('should clear existing interval', () => {
numberState.interval(dummyCallbackFunction);
const currentInterval = numberState.currentInterval;

numberState.clearInterval();

expect(clearInterval).toHaveBeenCalledTimes(1);
expect(clearInterval).toHaveBeenLastCalledWith(currentInterval);
expect(numberState.currentInterval).toBeUndefined();
});

it("shouldn't clear not existing interval", () => {
numberState.clearInterval();

expect(clearInterval).not.toHaveBeenCalled();
expect(numberState.currentInterval).toBeUndefined();
});
});

describe('copy function tests', () => {
it('should return a reference free copy of the current State Value', () => {
jest.spyOn(Utils, 'copy');
Expand Down