Skip to content

Commit 068e870

Browse files
authored
refactor(webapp): clarify deterministic time control naming in tests (#20586)
Replace "fake wall clock" terminology with "deterministic wall clock" to make the test-time semantics explicit and consistent across the webapp test suite.
1 parent 474539c commit 068e870

File tree

7 files changed

+166
-166
lines changed

7 files changed

+166
-166
lines changed

apps/webapp/src/script/application-periodic-checks/startApplicationPeriodicChecks.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,45 @@
1717
*
1818
*/
1919

20-
import {createFakeWallClock} from '../clock/fakeWallClock';
20+
import {createDeterministicWallClock} from '../clock/deterministicWallClock';
2121
import {startApplicationPeriodicChecks} from './startApplicationPeriodicChecks';
2222

2323
describe('startApplicationPeriodicChecks', () => {
2424
it('executes periodic checks immediately and again after each configured delay', () => {
25-
const fakeWallClock = createFakeWallClock({initialCurrentTimestampInMilliseconds: 0});
25+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
2626
const periodicChecksIntervalDelayInMilliseconds = 1_234;
2727
const runPeriodicCheck = jest.fn();
2828

2929
startApplicationPeriodicChecks({
30-
wallClock: fakeWallClock,
30+
wallClock: deterministicWallClock,
3131
periodicChecksIntervalDelayInMilliseconds,
3232
runPeriodicCheck,
3333
});
3434

3535
expect(runPeriodicCheck).toHaveBeenCalledTimes(1);
3636

37-
fakeWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds - 1);
37+
deterministicWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds - 1);
3838
expect(runPeriodicCheck).toHaveBeenCalledTimes(1);
3939

40-
fakeWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds);
40+
deterministicWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds);
4141

4242
expect(runPeriodicCheck).toHaveBeenCalledTimes(2);
4343
});
4444

4545
it('stops executing periodic checks after cleanup is called', () => {
46-
const fakeWallClock = createFakeWallClock({initialCurrentTimestampInMilliseconds: 0});
46+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
4747
const periodicChecksIntervalDelayInMilliseconds = 567;
4848
const runPeriodicCheck = jest.fn();
4949

5050
const stopPeriodicChecks = startApplicationPeriodicChecks({
51-
wallClock: fakeWallClock,
51+
wallClock: deterministicWallClock,
5252
periodicChecksIntervalDelayInMilliseconds,
5353
runPeriodicCheck,
5454
});
5555

56-
fakeWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds);
56+
deterministicWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds);
5757
stopPeriodicChecks();
58-
fakeWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds);
58+
deterministicWallClock.advanceByMilliseconds(periodicChecksIntervalDelayInMilliseconds);
5959

6060
expect(runPeriodicCheck).toHaveBeenCalledTimes(2);
6161
});
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2026 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {createDeterministicWallClock} from './deterministicWallClock';
21+
22+
describe('createDeterministicWallClock', () => {
23+
it('starts at zero by default', () => {
24+
const deterministicWallClock = createDeterministicWallClock();
25+
26+
expect(typeof deterministicWallClock.currentTimestampInMilliseconds).toBe('number');
27+
expect(deterministicWallClock.currentDate).toBeInstanceOf(Date);
28+
expect(deterministicWallClock.currentTimestampInMilliseconds).toBe(0);
29+
expect(deterministicWallClock.currentDate.getTime()).toBe(0);
30+
});
31+
32+
it('starts with the provided initial timestamp', () => {
33+
const initialCurrentTimestampInMilliseconds = 1_704_067_200_000;
34+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds});
35+
36+
expect(typeof deterministicWallClock.currentTimestampInMilliseconds).toBe('number');
37+
expect(deterministicWallClock.currentDate).toBeInstanceOf(Date);
38+
expect(deterministicWallClock.currentTimestampInMilliseconds).toBe(initialCurrentTimestampInMilliseconds);
39+
expect(deterministicWallClock.currentDate.getTime()).toBe(initialCurrentTimestampInMilliseconds);
40+
});
41+
42+
it('sets the current timestamp in milliseconds', () => {
43+
const deterministicWallClock = createDeterministicWallClock();
44+
const nextTimestampInMilliseconds = 1_800_000;
45+
46+
deterministicWallClock.setCurrentTimestampInMilliseconds(nextTimestampInMilliseconds);
47+
48+
expect(deterministicWallClock.currentTimestampInMilliseconds).toBe(nextTimestampInMilliseconds);
49+
expect(deterministicWallClock.currentDate.getTime()).toBe(nextTimestampInMilliseconds);
50+
});
51+
52+
it('advances current timestamp by the provided delay', () => {
53+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 10_000});
54+
55+
deterministicWallClock.advanceByMilliseconds(500);
56+
deterministicWallClock.advanceByMilliseconds(1_500);
57+
58+
expect(deterministicWallClock.currentTimestampInMilliseconds).toBe(12_000);
59+
expect(deterministicWallClock.currentDate.getTime()).toBe(12_000);
60+
});
61+
62+
it('returns a new date instance for each call', () => {
63+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 100});
64+
65+
const firstCurrentDate = deterministicWallClock.currentDate;
66+
const secondCurrentDate = deterministicWallClock.currentDate;
67+
68+
expect(firstCurrentDate).not.toBe(secondCurrentDate);
69+
expect(firstCurrentDate.getTime()).toBe(100);
70+
expect(secondCurrentDate.getTime()).toBe(100);
71+
});
72+
73+
it('executes interval callbacks repeatedly when time advances', () => {
74+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
75+
const intervalHandler = jest.fn();
76+
77+
deterministicWallClock.setInterval(intervalHandler, 100, 'interval argument');
78+
deterministicWallClock.advanceByMilliseconds(250);
79+
80+
expect(intervalHandler).toHaveBeenCalledTimes(2);
81+
expect(intervalHandler).toHaveBeenNthCalledWith(1, 'interval argument');
82+
expect(intervalHandler).toHaveBeenNthCalledWith(2, 'interval argument');
83+
});
84+
85+
it('stops executing callbacks after clearInterval', () => {
86+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
87+
const intervalHandler = jest.fn();
88+
89+
const intervalIdentifier = deterministicWallClock.setInterval(intervalHandler, 100);
90+
deterministicWallClock.advanceByMilliseconds(100);
91+
deterministicWallClock.clearInterval(intervalIdentifier);
92+
deterministicWallClock.advanceByMilliseconds(300);
93+
94+
expect(intervalHandler).toHaveBeenCalledTimes(1);
95+
});
96+
97+
it('executes timeout callback once when time reaches the delay', () => {
98+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
99+
const timeoutHandler = jest.fn();
100+
101+
deterministicWallClock.setTimeout(timeoutHandler, 100, 'timeout argument');
102+
deterministicWallClock.advanceByMilliseconds(100);
103+
deterministicWallClock.advanceByMilliseconds(500);
104+
105+
expect(timeoutHandler).toHaveBeenCalledTimes(1);
106+
expect(timeoutHandler).toHaveBeenNthCalledWith(1, 'timeout argument');
107+
});
108+
109+
it('does not execute timeout callback before the delay', () => {
110+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
111+
const timeoutHandler = jest.fn();
112+
113+
deterministicWallClock.setTimeout(timeoutHandler, 100);
114+
deterministicWallClock.advanceByMilliseconds(99);
115+
116+
expect(timeoutHandler).not.toHaveBeenCalled();
117+
});
118+
119+
it('stops executing timeout callback after clearTimeout', () => {
120+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
121+
const timeoutHandler = jest.fn();
122+
123+
const timeoutIdentifier = deterministicWallClock.setTimeout(timeoutHandler, 100);
124+
deterministicWallClock.clearTimeout(timeoutIdentifier);
125+
deterministicWallClock.advanceByMilliseconds(100);
126+
127+
expect(timeoutHandler).not.toHaveBeenCalled();
128+
});
129+
130+
it('executes due timeout callbacks in registration order for the same execution timestamp', () => {
131+
const deterministicWallClock = createDeterministicWallClock({initialCurrentTimestampInMilliseconds: 0});
132+
const executionOrder: string[] = [];
133+
134+
deterministicWallClock.setTimeout(() => executionOrder.push('first timeout'), 100);
135+
deterministicWallClock.setTimeout(() => executionOrder.push('second timeout'), 100);
136+
deterministicWallClock.setTimeout(() => executionOrder.push('third timeout'), 100);
137+
deterministicWallClock.advanceByMilliseconds(100);
138+
139+
expect(executionOrder).toEqual(['first timeout', 'second timeout', 'third timeout']);
140+
});
141+
});

apps/webapp/src/script/clock/fakeWallClock.ts renamed to apps/webapp/src/script/clock/deterministicWallClock.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import {WallClock} from './wallClock';
2121

22-
type FakeWallClockOptions = {
22+
type DeterministicWallClockOptions = {
2323
readonly initialCurrentTimestampInMilliseconds?: number;
2424
};
2525

@@ -34,12 +34,12 @@ type TimeoutRegistration = {
3434
readonly executionTimestampInMilliseconds: number;
3535
};
3636

37-
export type FakeWallClock = WallClock & {
37+
export type DeterministicWallClock = WallClock & {
3838
setCurrentTimestampInMilliseconds(nextTimestampInMilliseconds: number): void;
3939
advanceByMilliseconds(delayInMilliseconds: number): void;
4040
};
4141

42-
export function createFakeWallClock(options: FakeWallClockOptions = {}): FakeWallClock {
42+
export function createDeterministicWallClock(options: DeterministicWallClockOptions = {}): DeterministicWallClock {
4343
const {initialCurrentTimestampInMilliseconds = 0} = options;
4444

4545
let currentTimestampInMilliseconds = initialCurrentTimestampInMilliseconds;

apps/webapp/src/script/clock/fakeWallClock.test.ts

Lines changed: 0 additions & 141 deletions
This file was deleted.

apps/webapp/src/script/page/MainContent/MainContent.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {MainContent} from './MainContent';
2727

2828
import {withTheme} from '../../auth/util/test/TestUtil';
2929
import {MainViewModel} from '../../view_model/MainViewModel';
30-
import {createFakeWallClock} from '../../clock/fakeWallClock';
30+
import {createDeterministicWallClock} from '../../clock/deterministicWallClock';
3131
import {RootProvider} from '../RootProvider';
3232
import {ContentState, useAppState} from '../useAppState';
3333

@@ -66,7 +66,7 @@ describe('Preferences', () => {
6666
selfUser: new User('selfUser'),
6767
reloadApp: jest.fn(),
6868
};
69-
const wallClock = createFakeWallClock();
69+
const wallClock = createDeterministicWallClock();
7070

7171
it('renders the right component according to view state', () => {
7272
const {setContentState} = useAppState.getState();

0 commit comments

Comments
 (0)