odoo.define('mail/static/src/utils/timer/timer_tests.js', function (require) {
'use strict';

const {
    afterEach: utilsAfterEach,
    beforeEach: utilsBeforeEach,
    start,
} = require('mail/static/src/utils/test_utils.js');
const Timer = require('mail/static/src/utils/timer/timer.js');
const { nextTick } = require('mail/static/src/utils/test_utils.js');

const { TimerClearedError } = Timer;

QUnit.module('mail', {}, function () {
QUnit.module('utils', {}, function () {
QUnit.module('timer', {}, function () {
QUnit.module('timer_tests.js', {
    beforeEach() {
        utilsBeforeEach(this);
        this.timers = [];

        this.start = async (...args) => {
            const { env, widget } = await start(...args);
            this.env = env;
            this.widget = widget;
        };
    },
    afterEach() {
        utilsAfterEach(this);
        // Important: tests should cleanly intercept cancelation errors that
        // may result from this teardown.
        this.widget.destroy();
        for (const timer of this.timers) {
            timer.clear();
        }
    },
});

QUnit.test('timer does not timeout on initialization', async function (assert) {
    assert.expect(3);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            0
        )
    );

    assert.notOk(
        hasTimedOut,
        "timer should not have timed out on immediate initialization"
    );

    await this.env.testUtils.advanceTime(0);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out from initialization after 0ms"
    );

    await this.env.testUtils.advanceTime(1000 * 1000);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out from initialization after 1000s"
    );
});

QUnit.test('timer start (duration: 0ms)', async function (assert) {
    assert.expect(2);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            0
        )
    );

    this.timers[0].start();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );

    await this.env.testUtils.advanceTime(0);
    assert.ok(
        hasTimedOut,
        "timer should have timed out on start after 0ms"
    );
});

QUnit.test('timer start observe termination (duration: 0ms)', async function (assert) {
    assert.expect(6);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => {
                hasTimedOut = true;
                return 'timeout_result';
            },
            0
        )
    );

    this.timers[0].start()
        .then(result => {
            assert.strictEqual(
                result,
                'timeout_result',
                "value returned by start should be value returned by function on timeout"
            );
            assert.step('timeout');
        });
    await nextTick();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );
    assert.verifySteps(
        [],
        "timer.start() should not have yet observed timeout"
    );

    await this.env.testUtils.advanceTime(0);
    assert.ok(
        hasTimedOut,
        "timer should have timed out on start after 0ms"
    );
    assert.verifySteps(
        ['timeout'],
        "timer.start() should have observed timeout after 0ms"
    );
});

QUnit.test('timer start (duration: 1000s)', async function (assert) {
    assert.expect(5);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            1000 * 1000
        )
    );

    this.timers[0].start();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );

    await this.env.testUtils.advanceTime(0);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out on start after 0ms"
    );

    await this.env.testUtils.advanceTime(1000);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out on start after 1000ms"
    );

    await this.env.testUtils.advanceTime(998 * 1000 + 999);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out on start after 9999ms"
    );

    await this.env.testUtils.advanceTime(1);
    assert.ok(
        hasTimedOut,
        "timer should have timed out on start after 10s"
    );
});

QUnit.test('[no cancelation intercept] timer start then immediate clear (duration: 0ms)', async function (assert) {
    assert.expect(4);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            0
        )
    );

    this.timers[0].start();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );

    this.timers[0].clear();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start and clear"
    );

    await this.env.testUtils.advanceTime(0);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 0ms of clear"
    );

    await this.env.testUtils.advanceTime(1000);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 1s of clear"
    );
});

QUnit.test('[no cancelation intercept] timer start then clear before timeout (duration: 1000ms)', async function (assert) {
    assert.expect(4);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            1000
        )
    );

    this.timers[0].start();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );

    await this.env.testUtils.advanceTime(999);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after 999ms of start"
    );

    this.timers[0].clear();
    await this.env.testUtils.advanceTime(1);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 1ms of clear that happens 999ms after start (globally 1s await)"
    );

    await this.env.testUtils.advanceTime(1000);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 1001ms after clear (timer fully cleared)"
    );
});

QUnit.test('[no cancelation intercept] timer start then reset before timeout (duration: 1000ms)', async function (assert) {
    assert.expect(5);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            1000
        )
    );

    this.timers[0].start();
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );

    await this.env.testUtils.advanceTime(999);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 999ms of start"
    );

    this.timers[0].reset();
    await this.env.testUtils.advanceTime(1);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 1ms of reset which happens 999ms after start"
    );

    await this.env.testUtils.advanceTime(998);
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out after 999ms of reset"
    );

    await this.env.testUtils.advanceTime(1);
    assert.ok(
        hasTimedOut,
        "timer should not have timed out after 1s of reset"
    );
});

QUnit.test('[with cancelation intercept] timer start then immediate clear (duration: 0ms)', async function (assert) {
    assert.expect(5);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            0,
            { silentCancelationErrors: false }
        )
    );

    this.timers[0].start()
        .then(() => {
            throw new Error("timer.start() should not be resolved (should have been canceled by clear)");
        })
        .catch(error => {
            assert.ok(
                error instanceof TimerClearedError,
                "Should generate a Timer cleared error (from `.clear()`)"
            );
            assert.step('timer_cleared');
        });
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );
    await nextTick();
    assert.verifySteps([], "should not have observed cleared timer (timer not yet cleared)");

    this.timers[0].clear();
    await nextTick();
    assert.verifySteps(
        ['timer_cleared'],
        "timer.start() should have observed it has been cleared"
    );
});

QUnit.test('[with cancelation intercept] timer start then immediate reset (duration: 0ms)', async function (assert) {
    assert.expect(9);

    await this.start({
        hasTimeControl: true,
    });

    let hasTimedOut = false;
    this.timers.push(
        new Timer(
            this.env,
            () => hasTimedOut = true,
            0,
            { silentCancelationErrors: false }
        )
    );

    this.timers[0].start()
        .then(() => {
            throw new Error("timer.start() should not observe a timeout");
        })
        .catch(error => {
            assert.ok(error instanceof TimerClearedError, "Should generate a Timer cleared error (from `.reset()`)");
            assert.step('timer_cleared');
        });
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after start"
    );
    await nextTick();
    assert.verifySteps([], "should not have observed cleared timer (timer not yet cleared)");

    this.timers[0].reset()
        .then(() => assert.step('timer_reset_timeout'));
    await nextTick();
    assert.verifySteps(
        ['timer_cleared'],
        "timer.start() should have observed it has been cleared"
    );
    assert.notOk(
        hasTimedOut,
        "timer should not have timed out immediately after reset"
    );

    await this.env.testUtils.advanceTime(0);
    assert.ok(
        hasTimedOut,
        "timer should have timed out after reset timeout"
    );
    assert.verifySteps(
        ['timer_reset_timeout'],
        "timer.reset() should have observed it has timed out"
    );
});

});
});
});

});
