Skip to content

Commit

Permalink
Merge faf1b9f into 5154d41
Browse files Browse the repository at this point in the history
  • Loading branch information
bendikjohansen committed Oct 22, 2022
2 parents 5154d41 + faf1b9f commit aa43d9c
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ If the current date and time (determined via a reference to `Date.now`) is not t
`onTick` is a callback and triggered every time a new period is started, based on what the [`intervalDelay`](#intervaldelay)'s value is. It only gets triggered when the countdown's [`controlled`](#controlled) prop is set to `false`, meaning that the countdown has full control over its interval. It receives a [time delta object](#calctimedelta) as the first argument.

### `onComplete`
`onComplete` is a callback and triggered whenever the countdown ends. In contrast to [`onTick`](#ontick), the [`onComplete`](#oncomplete) callback also gets triggered in case [`controlled`](#controlled) is set to `true`. It receives a [time delta object](#calctimedelta) as the first argument.
`onComplete` is a callback and triggered whenever the countdown ends. In contrast to [`onTick`](#ontick), the [`onComplete`](#oncomplete) callback also gets triggered in case [`controlled`](#controlled) is set to `true`. It receives a [time delta object](#calctimedelta) as the first argument and a `boolean` as a second argument, indicating whether the countdown transitioned into the completed state (`false`) or completed on start (`true`).

## API Reference

Expand Down
45 changes: 40 additions & 5 deletions src/Countdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { mount, ReactWrapper } from 'enzyme';

import Countdown, { CountdownProps } from './Countdown';
import { calcTimeDelta, formatTimeDelta } from './utils';
import { calcTimeDelta, CountdownTimeDelta, formatTimeDelta } from './utils';

import { CountdownProps as LegacyCountdownProps } from './LegacyCountdown';

Expand Down Expand Up @@ -56,7 +56,7 @@ describe('<Countdown />', () => {
const zeroPadTime = 0;

class Completionist extends React.Component<any> {
componentDidMount() {}
componentDidMount() { }

render() {
return (
Expand Down Expand Up @@ -157,11 +157,46 @@ describe('<Countdown />', () => {
seconds: 1,
});

expect(onComplete.mock.calls.length).toBe(1);
expect(onComplete).toBeCalledWith({ ...defaultStats, completed: true });
expect(onComplete).toBeCalledTimes(1);
expect(onComplete).toBeCalledWith({ ...defaultStats, completed: true }, false);
expect(wrapper.state().timeDelta.completed).toBe(true);
});

it('should trigger various callbacks before onComplete is called', () => {
const calls: string[] = [];

const onStart = jest.fn().mockImplementation(() => calls.push('onStart'));
const onTick = jest.fn().mockImplementation(() => calls.push('onTick'));
const onComplete = jest.fn().mockImplementation(() => calls.push('onComplete'));
wrapper = mount(<Countdown date={countdownDate} onStart={onStart} onTick={onTick} onComplete={onComplete} />);

expect(calls).toEqual(['onStart']);

for (let i = 1; i <= 10; i++) {
now.mockReturnValue(countdownDate - countdownMs + i * 1000);
jest.runTimersToTime(1000);
}

expect(calls).toEqual(['onStart', ...(new Array(9).fill('onTick')), 'onComplete']);
});

it('should trigger onComplete callback on start if date is in the past when countdown starts', () => {
const calls: string[] = [];

const onStart = jest.fn().mockImplementation(() => calls.push('onStart'));
const onTick = jest.fn().mockImplementation(() => calls.push('onTick'));
const onComplete = jest.fn().mockImplementation(() => calls.push('onComplete'));

countdownDate = Date.now() - 10000;
wrapper = mount(<Countdown date={countdownDate} onStart={onStart} onTick={onTick} onComplete={onComplete} />);

expect(onStart).toHaveBeenCalledTimes(1);
expect(onTick).not.toHaveBeenCalled();
expect(onComplete).toHaveBeenCalledTimes(1);
expect(onComplete).toBeCalledWith({ ...defaultStats, completed: true }, true);
expect(calls).toEqual(['onStart', 'onComplete']);
});

it('should run through the controlled component by updating the date prop', () => {
const root = document.createElement('div');
wrapper = mount(<Countdown date={1000} controlled />, { attachTo: root });
Expand Down Expand Up @@ -600,6 +635,6 @@ describe('<Countdown />', () => {
afterEach(() => {
try {
wrapper.detach();
} catch (e) {}
} catch (e) { }
});
});
23 changes: 11 additions & 12 deletions src/Countdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {

export interface CountdownProps
extends React.Props<Countdown>,
CountdownTimeDeltaFormatOptions,
Omit<LegacyCountdownProps, 'onComplete'> {
CountdownTimeDeltaFormatOptions,
Omit<LegacyCountdownProps, 'onComplete'> {
readonly date?: Date | number | string;
readonly controlled?: boolean;
readonly intervalDelay?: number;
Expand All @@ -31,7 +31,7 @@ export interface CountdownProps
readonly onPause?: CountdownTimeDeltaFn;
readonly onStop?: CountdownTimeDeltaFn;
readonly onTick?: CountdownTimeDeltaFn;
readonly onComplete?: CountdownTimeDeltaFn | LegacyCountdownProps['onComplete'];
readonly onComplete?: (timeDelta: CountdownTimeDelta, completedOnStart: boolean) => void | LegacyCountdownProps['onComplete'];
}

export interface CountdownRenderProps extends CountdownTimeDelta {
Expand Down Expand Up @@ -246,27 +246,26 @@ export default class Countdown extends React.Component<CountdownProps, Countdown
return this.state.status === status;
}

handleOnComplete = (timeDelta: CountdownTimeDelta): void => {
if (this.props.onComplete) this.props.onComplete(timeDelta);
};

setTimeDeltaState(
timeDelta: CountdownTimeDelta,
status?: CountdownStatus,
callback?: (timeDelta: CountdownTimeDelta) => void
): void {
if (!this.mounted) return;

let completedCallback: this['handleOnComplete'] | undefined;
const completing = timeDelta.completed && !this.state.timeDelta.completed;
const completedOnStart = timeDelta.completed && status === CountdownStatus.STARTED;

if (!this.state.timeDelta.completed && timeDelta.completed) {
if (!this.props.overtime) this.clearTimer();
completedCallback = this.handleOnComplete;
if (completing && !this.props.overtime) {
this.clearTimer();
}

const onDone = () => {
if (callback) callback(this.state.timeDelta);
if (completedCallback) completedCallback(this.state.timeDelta);

if (this.props.onComplete && (completing || completedOnStart)) {
this.props.onComplete(timeDelta, completedOnStart);
}
};

return this.setState(prevState => {
Expand Down

0 comments on commit aa43d9c

Please sign in to comment.