p2p-lockstep-kit-session 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import assert from "node:assert/strict";
2
- import { createSession } from "../dist/session/index.js";
1
+ import assert from 'node:assert/strict';
2
+ import { createSession } from '../dist/session/index.js';
3
3
 
4
4
  const waitForBus = () => new Promise((resolve) => setTimeout(resolve, 0));
5
5
 
@@ -14,7 +14,7 @@ class BoundaryClient {
14
14
 
15
15
  onStateChange(handler) {
16
16
  this.stateHandler = handler;
17
- handler("passive");
17
+ handler('passive');
18
18
  }
19
19
 
20
20
  onRemoteStream() {}
@@ -24,7 +24,7 @@ class BoundaryClient {
24
24
  }
25
25
 
26
26
  connect() {
27
- this.stateHandler?.("connected");
27
+ this.stateHandler?.('connected');
28
28
  }
29
29
 
30
30
  inbound(data) {
@@ -34,9 +34,9 @@ class BoundaryClient {
34
34
 
35
35
  const createConnectedSession = () => {
36
36
  const client = new BoundaryClient();
37
- const session = createSession(client, "demo-room");
37
+ const session = createSession(client, 'demo-room');
38
38
 
39
- session.net.setPeerIds({ local: "local", remote: "remote" });
39
+ session.net.setPeerIds({ local: 'local', remote: 'remote' });
40
40
  client.connect();
41
41
 
42
42
  return { client, session };
@@ -44,23 +44,51 @@ const createConnectedSession = () => {
44
44
 
45
45
  const startGame = async () => {
46
46
  const runtime = createConnectedSession();
47
- runtime.client.inbound({ type: "READY", sid: "demo-room", from: "remote" });
47
+ runtime.client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
48
48
  await waitForBus();
49
49
  runtime.session.actions.start();
50
50
  await waitForBus();
51
51
 
52
+ assert.match(runtime.session.state.getState('local'), /^(turn|remote_turn)$/);
52
53
  assert.match(
53
- runtime.session.state.getState("local"),
54
+ runtime.session.state.getState('remote'),
54
55
  /^(turn|remote_turn)$/,
55
56
  );
56
- assert.match(
57
- runtime.session.state.getState("remote"),
58
- /^(turn|remote_turn)$/,
57
+
58
+ return runtime;
59
+ };
60
+
61
+ const startGameWithFirstPlayer = async (firstPlayer) => {
62
+ const runtime = createConnectedSession();
63
+ runtime.client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
64
+ await waitForBus();
65
+ runtime.session.state.setLastStart(
66
+ firstPlayer === 'local' ? 'remote' : 'local',
67
+ );
68
+ runtime.session.actions.start();
69
+ await waitForBus();
70
+
71
+ assert.equal(
72
+ runtime.session.state.getState('local'),
73
+ firstPlayer === 'local' ? 'turn' : 'remote_turn',
74
+ );
75
+ assert.equal(
76
+ runtime.session.state.getState('remote'),
77
+ firstPlayer === 'local' ? 'remote_turn' : 'turn',
59
78
  );
60
79
 
61
80
  return runtime;
62
81
  };
63
82
 
83
+ const oneMoveWinPlugin = {
84
+ validateMove() {
85
+ return { valid: true };
86
+ },
87
+ checkWin(_gameState, history) {
88
+ return history.at(-1)?.player ?? null;
89
+ },
90
+ };
91
+
64
92
  {
65
93
  const { client, session } = createConnectedSession();
66
94
  await waitForBus();
@@ -69,22 +97,22 @@ const startGame = async () => {
69
97
  await waitForBus();
70
98
 
71
99
  const sent = client.sent.at(-1);
72
- assert.equal(typeof sent, "object");
73
- assert.equal(sent.type, "READY");
74
- assert.equal(sent.sid, "demo-room");
75
- assert.equal(session.state.getState("local"), "ready");
76
- assert.equal(session.state.getState("remote"), "could_start");
100
+ assert.equal(typeof sent, 'object');
101
+ assert.equal(sent.type, 'READY');
102
+ assert.equal(sent.sid, 'demo-room');
103
+ assert.equal(session.state.getState('local'), 'ready');
104
+ assert.equal(session.state.getState('remote'), 'could_start');
77
105
  }
78
106
 
79
107
  {
80
108
  const { client, session } = createConnectedSession();
81
109
  await waitForBus();
82
110
 
83
- client.inbound({ type: "READY", sid: "demo-room", from: "remote" });
111
+ client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
84
112
  await waitForBus();
85
113
 
86
- assert.equal(session.state.getState("local"), "could_start");
87
- assert.equal(session.state.getState("remote"), "ready");
114
+ assert.equal(session.state.getState('local'), 'could_start');
115
+ assert.equal(session.state.getState('remote'), 'ready');
88
116
  }
89
117
 
90
118
  {
@@ -92,12 +120,12 @@ const startGame = async () => {
92
120
  await waitForBus();
93
121
 
94
122
  client.inbound(
95
- JSON.stringify({ type: "READY", sid: "demo-room", from: "remote" }),
123
+ JSON.stringify({ type: 'READY', sid: 'demo-room', from: 'remote' }),
96
124
  );
97
125
  await waitForBus();
98
126
 
99
- assert.equal(session.state.getState("local"), "could_start");
100
- assert.equal(session.state.getState("remote"), "ready");
127
+ assert.equal(session.state.getState('local'), 'could_start');
128
+ assert.equal(session.state.getState('remote'), 'ready');
101
129
  }
102
130
 
103
131
  {
@@ -113,51 +141,276 @@ const startGame = async () => {
113
141
  session.actions.restart();
114
142
  await waitForBus();
115
143
 
116
- assert.equal(session.state.getPendingAction(), "restart");
117
- assert.equal(session.state.getState("local"), "waiting_approval");
118
- assert.equal(session.state.getState("remote"), "approving");
144
+ assert.equal(session.state.getPendingAction(), 'restart');
145
+ assert.equal(session.state.getState('local'), 'waiting_approval');
146
+ assert.equal(session.state.getState('remote'), 'approving');
119
147
 
120
148
  client.inbound({
121
- type: "APPROVE",
122
- payload: { action: "restart" },
123
- from: "remote",
149
+ type: 'APPROVE',
150
+ payload: { action: 'restart' },
151
+ from: 'remote',
124
152
  });
125
153
  await waitForBus();
126
154
 
127
155
  assert.equal(session.state.getPendingAction(), null);
128
- assert.equal(session.state.getState("local"), "idle");
129
- assert.equal(session.state.getState("remote"), "idle");
156
+ assert.equal(session.state.getState('local'), 'idle');
157
+ assert.equal(session.state.getState('remote'), 'idle');
130
158
  assert.equal(
131
159
  snapshots.some(
132
160
  (snapshot) =>
133
- snapshot.localState === "idle" &&
134
- snapshot.remoteState === "idle" &&
135
- snapshot.pendingAction === "restart",
161
+ snapshot.localState === 'idle' &&
162
+ snapshot.remoteState === 'idle' &&
163
+ snapshot.pendingAction === 'restart',
136
164
  ),
137
165
  false,
138
166
  );
139
167
  }
140
168
 
169
+ {
170
+ const { client, session } = await startGame();
171
+ const snapshots = [];
172
+ session.observer.subscribe({
173
+ onStateChange(snapshot) {
174
+ snapshots.push(snapshot);
175
+ },
176
+ onGameEvent() {},
177
+ });
178
+
179
+ if (session.state.getState('local') !== 'turn') {
180
+ client.inbound({
181
+ type: 'MOVE',
182
+ payload: { step: 'remote-first' },
183
+ from: 'remote',
184
+ });
185
+ await waitForBus();
186
+ snapshots.splice(0, snapshots.length);
187
+ }
188
+
189
+ const beforeHistory = session.state.getHistory().length;
190
+ session.actions.move({ step: 'local-move' });
191
+ await waitForBus();
192
+
193
+ assert.equal(session.state.getHistory().length, beforeHistory + 1);
194
+ assert.equal(
195
+ snapshots.some((snapshot) => snapshot.history.length === beforeHistory + 1),
196
+ true,
197
+ );
198
+ }
199
+
200
+ {
201
+ const { client, session } = await startGameWithFirstPlayer('local');
202
+ session.state.setGamePlugin(oneMoveWinPlugin);
203
+
204
+ session.actions.move({ step: 'winning-local-move' });
205
+ await waitForBus();
206
+
207
+ assert.equal(client.sent.at(-1).type, 'MOVE');
208
+ assert.equal(session.state.getState('local'), 'idle');
209
+ assert.equal(session.state.getState('remote'), 'idle');
210
+ assert.equal(session.state.getHistory().length, 1);
211
+ assert.equal(session.state.getHistory()[0].move.step, 'winning-local-move');
212
+ }
213
+
214
+ {
215
+ const { client, session } = await startGameWithFirstPlayer('remote');
216
+ session.state.setGamePlugin(oneMoveWinPlugin);
217
+
218
+ client.inbound({
219
+ type: 'MOVE',
220
+ payload: { step: 'winning-remote-move' },
221
+ from: 'remote',
222
+ });
223
+ await waitForBus();
224
+
225
+ assert.equal(session.state.getState('local'), 'idle');
226
+ assert.equal(session.state.getState('remote'), 'idle');
227
+ assert.equal(session.state.getHistory().length, 1);
228
+ assert.equal(session.state.getHistory()[0].move.step, 'winning-remote-move');
229
+ }
230
+
231
+ {
232
+ const { client, session } = await startGameWithFirstPlayer('local');
233
+ session.state.setGamePlugin(oneMoveWinPlugin);
234
+
235
+ session.actions.move({ step: 'winning-local-move' });
236
+ await waitForBus();
237
+ assert.equal(session.state.getHistory().length, 1);
238
+ assert.equal(session.state.getState('local'), 'idle');
239
+ assert.equal(session.state.getState('remote'), 'idle');
240
+
241
+ client.inbound({ type: 'READY', sid: 'demo-room', from: 'remote' });
242
+ await waitForBus();
243
+ session.actions.start();
244
+ await waitForBus();
245
+
246
+ assert.equal(session.state.getHistory().length, 0);
247
+ assert.match(session.state.getState('local'), /^(turn|remote_turn)$/);
248
+ assert.match(session.state.getState('remote'), /^(turn|remote_turn)$/);
249
+ }
250
+
251
+ {
252
+ const { client, session } = await startGameWithFirstPlayer('remote');
253
+ session.state.setGamePlugin(oneMoveWinPlugin);
254
+
255
+ client.inbound({
256
+ type: 'MOVE',
257
+ payload: { step: 'winning-remote-move' },
258
+ from: 'remote',
259
+ });
260
+ await waitForBus();
261
+ assert.equal(session.state.getHistory().length, 1);
262
+ assert.equal(session.state.getState('local'), 'idle');
263
+ assert.equal(session.state.getState('remote'), 'idle');
264
+
265
+ session.actions.ready();
266
+ await waitForBus();
267
+ client.inbound({
268
+ type: 'START',
269
+ payload: { starter: 'sender' },
270
+ from: 'remote',
271
+ });
272
+ await waitForBus();
273
+
274
+ assert.equal(session.state.getHistory().length, 0);
275
+ assert.equal(session.state.getState('local'), 'remote_turn');
276
+ assert.equal(session.state.getState('remote'), 'turn');
277
+ }
278
+
279
+ {
280
+ const { client, session } = await startGameWithFirstPlayer('local');
281
+
282
+ session.actions.move({ step: 'local-just-moved' });
283
+ await waitForBus();
284
+ assert.equal(session.state.getState('local'), 'remote_turn');
285
+ assert.equal(session.state.getHistory().length, 1);
286
+
287
+ session.actions.undo();
288
+ await waitForBus();
289
+
290
+ assert.equal(client.sent.at(-1).type, 'UNDO');
291
+ assert.equal(client.sent.at(-1).payload.count, 1);
292
+ assert.equal(session.state.getPendingUndoCount(), 1);
293
+
294
+ client.inbound({
295
+ type: 'APPROVE',
296
+ payload: { action: 'undo' },
297
+ from: 'remote',
298
+ });
299
+ await waitForBus();
300
+
301
+ assert.equal(session.state.getHistory().length, 0);
302
+ assert.equal(session.state.getState('local'), 'turn');
303
+ assert.equal(session.state.getState('remote'), 'remote_turn');
304
+ }
305
+
306
+ {
307
+ const { client, session } = await startGameWithFirstPlayer('local');
308
+
309
+ session.actions.move({ step: 'local-first' });
310
+ await waitForBus();
311
+ client.inbound({
312
+ type: 'MOVE',
313
+ payload: { step: 'remote-reply' },
314
+ from: 'remote',
315
+ });
316
+ await waitForBus();
317
+ assert.equal(session.state.getState('local'), 'turn');
318
+ assert.equal(session.state.getHistory().length, 2);
319
+
320
+ session.actions.undo();
321
+ await waitForBus();
322
+
323
+ assert.equal(client.sent.at(-1).type, 'UNDO');
324
+ assert.equal(client.sent.at(-1).payload.count, 2);
325
+ assert.equal(session.state.getPendingUndoCount(), 2);
326
+
327
+ client.inbound({
328
+ type: 'APPROVE',
329
+ payload: { action: 'undo' },
330
+ from: 'remote',
331
+ });
332
+ await waitForBus();
333
+
334
+ assert.equal(session.state.getHistory().length, 0);
335
+ assert.equal(session.state.getState('local'), 'turn');
336
+ assert.equal(session.state.getState('remote'), 'remote_turn');
337
+ }
338
+
339
+ {
340
+ const { client, session } = await startGameWithFirstPlayer('local');
341
+
342
+ session.actions.move({ step: 'local-just-moved' });
343
+ await waitForBus();
344
+ assert.equal(session.state.getState('local'), 'remote_turn');
345
+
346
+ session.actions.undo();
347
+ await waitForBus();
348
+ client.inbound({
349
+ type: 'REJECT',
350
+ payload: { action: 'undo' },
351
+ from: 'remote',
352
+ });
353
+ await waitForBus();
354
+
355
+ assert.equal(session.state.getHistory().length, 1);
356
+ assert.equal(session.state.getState('local'), 'remote_turn');
357
+ assert.equal(session.state.getState('remote'), 'turn');
358
+ assert.equal(
359
+ client.sent.some((message) => message.type === 'UNDO'),
360
+ true,
361
+ );
362
+ }
363
+
364
+ {
365
+ const { client } = createConnectedSession();
366
+ await waitForBus();
367
+
368
+ client.inbound({ type: 'UNDO', payload: { count: 3 }, from: 'remote' });
369
+ await waitForBus();
370
+ await waitForBus();
371
+
372
+ const sent = client.sent.at(-1);
373
+ assert.equal(sent.type, 'REJECT');
374
+ assert.equal(sent.payload.action, 'undo');
375
+ assert.equal(sent.payload.reason, 'invalid_state');
376
+ }
377
+
378
+ {
379
+ const { client, session } = createConnectedSession();
380
+ await waitForBus();
381
+
382
+ session.actions.ready();
383
+ await waitForBus();
384
+ client.inbound({ type: 'RESTART', from: 'remote' });
385
+ await waitForBus();
386
+ await waitForBus();
387
+
388
+ const sent = client.sent.at(-1);
389
+ assert.equal(sent.type, 'REJECT');
390
+ assert.equal(sent.payload.action, 'restart');
391
+ assert.equal(sent.payload.reason, 'invalid_state');
392
+ }
393
+
141
394
  {
142
395
  const { client, session } = await startGame();
143
396
  const beforeRestart = {
144
- local: session.state.getState("local"),
145
- remote: session.state.getState("remote"),
397
+ local: session.state.getState('local'),
398
+ remote: session.state.getState('remote'),
146
399
  };
147
400
 
148
401
  session.actions.restart();
149
402
  await waitForBus();
150
403
 
151
404
  client.inbound({
152
- type: "REJECT",
153
- payload: { action: "restart" },
154
- from: "remote",
405
+ type: 'REJECT',
406
+ payload: { action: 'restart' },
407
+ from: 'remote',
155
408
  });
156
409
  await waitForBus();
157
410
 
158
411
  assert.equal(session.state.getPendingAction(), null);
159
- assert.equal(session.state.getState("local"), beforeRestart.local);
160
- assert.equal(session.state.getState("remote"), beforeRestart.remote);
412
+ assert.equal(session.state.getState('local'), beforeRestart.local);
413
+ assert.equal(session.state.getState('remote'), beforeRestart.remote);
161
414
  }
162
415
 
163
416
  {
@@ -165,42 +418,23 @@ const startGame = async () => {
165
418
  session.actions.restart();
166
419
  await waitForBus();
167
420
 
168
- assert.equal(session.state.getPendingAction(), "restart");
421
+ assert.equal(session.state.getPendingAction(), 'restart');
169
422
 
170
- session.bus.dispatch({ type: "OFFLINE", from: "local" });
423
+ session.bus.dispatch({ type: 'OFFLINE', from: 'local' });
171
424
  await waitForBus();
172
425
 
173
426
  assert.equal(session.state.getPendingAction(), null);
174
- assert.equal(session.state.getState("local"), "syncing");
175
- assert.equal(session.state.getState("remote"), "offline");
427
+ assert.equal(session.state.getState('local'), 'syncing');
428
+ assert.equal(session.state.getState('remote'), 'offline');
176
429
 
177
- session.bus.dispatch({ type: "ONLINE", from: "local" });
178
- await waitForBus();
430
+ session.bus.dispatch({ type: 'ONLINE', from: 'local' });
179
431
  await waitForBus();
180
-
181
- assert.equal(session.state.getPendingAction(), null);
182
- assert.equal(session.state.getState("local"), "syncing");
183
- assert.equal(session.state.getState("remote"), "syncing");
184
- assert.equal(client.sent.at(-1).type, "SYNC_REQUEST");
185
-
186
- client.inbound({
187
- type: "SYNC_STATE",
188
- payload: {
189
- history: [{ turn: 1, player: "local", move: { step: "peer-move" } }],
190
- lastStart: "local",
191
- turn: "local",
192
- resumeTurn: "local",
193
- },
194
- from: "remote",
195
- });
196
432
  await waitForBus();
197
433
 
198
434
  assert.equal(session.state.getPendingAction(), null);
199
- assert.equal(session.state.getState("local"), "remote_turn");
200
- assert.equal(session.state.getState("remote"), "turn");
201
- assert.equal(session.state.getHistory().length, 1);
202
- assert.equal(session.state.getHistory()[0].player, "remote");
203
- assert.equal(session.state.getLastStart(), "remote");
435
+ assert.equal(client.sent.at(-1).type, 'SYNC_STATE');
436
+ assert.match(session.state.getState('local'), /^(turn|remote_turn)$/);
437
+ assert.match(session.state.getState('remote'), /^(turn|remote_turn)$/);
204
438
  }
205
439
 
206
440
  {
@@ -213,28 +447,28 @@ const startGame = async () => {
213
447
  onGameEvent() {},
214
448
  });
215
449
 
216
- client.inbound({ type: "RESTART", from: "remote" });
450
+ client.inbound({ type: 'RESTART', from: 'remote' });
217
451
  await waitForBus();
218
452
 
219
- assert.equal(session.state.getPendingAction(), "restart");
220
- assert.equal(session.state.getState("local"), "approving");
221
- assert.equal(session.state.getState("remote"), "waiting_approval");
453
+ assert.equal(session.state.getPendingAction(), 'restart');
454
+ assert.equal(session.state.getState('local'), 'approving');
455
+ assert.equal(session.state.getState('remote'), 'waiting_approval');
222
456
 
223
457
  session.actions.approve();
224
458
  await waitForBus();
225
459
 
226
460
  const sent = client.sent.at(-1);
227
- assert.equal(sent.type, "APPROVE");
228
- assert.equal(sent.payload.action, "restart");
461
+ assert.equal(sent.type, 'APPROVE');
462
+ assert.equal(sent.payload.action, 'restart');
229
463
  assert.equal(session.state.getPendingAction(), null);
230
- assert.equal(session.state.getState("local"), "idle");
231
- assert.equal(session.state.getState("remote"), "idle");
464
+ assert.equal(session.state.getState('local'), 'idle');
465
+ assert.equal(session.state.getState('remote'), 'idle');
232
466
  assert.equal(
233
467
  snapshots.some(
234
468
  (snapshot) =>
235
- snapshot.localState === "idle" &&
236
- snapshot.remoteState === "idle" &&
237
- snapshot.pendingAction === "restart",
469
+ snapshot.localState === 'idle' &&
470
+ snapshot.remoteState === 'idle' &&
471
+ snapshot.pendingAction === 'restart',
238
472
  ),
239
473
  false,
240
474
  );
@@ -243,65 +477,96 @@ const startGame = async () => {
243
477
  {
244
478
  const { client, session } = await startGame();
245
479
  const beforeRestart = {
246
- local: session.state.getState("local"),
247
- remote: session.state.getState("remote"),
480
+ local: session.state.getState('local'),
481
+ remote: session.state.getState('remote'),
248
482
  };
249
483
 
250
- client.inbound({ type: "RESTART", from: "remote" });
484
+ client.inbound({ type: 'RESTART', from: 'remote' });
251
485
  await waitForBus();
252
486
 
253
487
  session.actions.reject();
254
488
  await waitForBus();
255
489
 
256
490
  const sent = client.sent.at(-1);
257
- assert.equal(sent.type, "REJECT");
258
- assert.equal(sent.payload.action, "restart");
491
+ assert.equal(sent.type, 'REJECT');
492
+ assert.equal(sent.payload.action, 'restart');
259
493
  assert.equal(session.state.getPendingAction(), null);
260
- assert.equal(session.state.getState("local"), beforeRestart.local);
261
- assert.equal(session.state.getState("remote"), beforeRestart.remote);
494
+ assert.equal(session.state.getState('local'), beforeRestart.local);
495
+ assert.equal(session.state.getState('remote'), beforeRestart.remote);
262
496
  }
263
497
 
264
498
  {
265
499
  const { client, session } = await startGame();
266
- client.inbound({ type: "RESTART", from: "remote" });
500
+ client.inbound({ type: 'RESTART', from: 'remote' });
267
501
  await waitForBus();
268
502
 
269
- assert.equal(session.state.getPendingAction(), "restart");
503
+ assert.equal(session.state.getPendingAction(), 'restart');
270
504
 
271
- session.bus.dispatch({ type: "OFFLINE", from: "local" });
505
+ session.bus.dispatch({ type: 'OFFLINE', from: 'local' });
272
506
  await waitForBus();
273
507
 
274
508
  assert.equal(session.state.getPendingAction(), null);
275
- assert.equal(session.state.getState("local"), "syncing");
276
- assert.equal(session.state.getState("remote"), "offline");
509
+ assert.equal(session.state.getState('local'), 'syncing');
510
+ assert.equal(session.state.getState('remote'), 'offline');
277
511
 
278
- session.bus.dispatch({ type: "ONLINE", from: "local" });
512
+ session.bus.dispatch({ type: 'ONLINE', from: 'local' });
279
513
  await waitForBus();
280
514
  await waitForBus();
281
515
 
282
516
  assert.equal(session.state.getPendingAction(), null);
283
- assert.equal(session.state.getState("local"), "syncing");
284
- assert.equal(session.state.getState("remote"), "syncing");
285
- assert.equal(client.sent.at(-1).type, "SYNC_REQUEST");
517
+ assert.equal(client.sent.at(-1).type, 'SYNC_STATE');
518
+ assert.match(session.state.getState('local'), /^(turn|remote_turn)$/);
519
+ assert.match(session.state.getState('remote'), /^(turn|remote_turn)$/);
520
+ }
521
+
522
+ {
523
+ const { client, session } = createConnectedSession();
524
+ await waitForBus();
286
525
 
287
526
  client.inbound({
288
- type: "SYNC_STATE",
527
+ type: 'SYNC_STATE',
289
528
  payload: {
290
- history: [{ turn: 1, player: "remote", move: { step: "local-move" } }],
291
- lastStart: "remote",
292
- turn: "remote",
293
- resumeTurn: "remote",
529
+ history: [{ turn: 1, player: 'local', move: { step: 'peer-move' } }],
530
+ lastStart: 'local',
531
+ turn: 'remote',
532
+ resumeTurn: 'remote',
294
533
  },
295
- from: "remote",
534
+ from: 'remote',
296
535
  });
297
536
  await waitForBus();
298
537
 
299
- assert.equal(session.state.getPendingAction(), null);
300
- assert.equal(session.state.getState("local"), "turn");
301
- assert.equal(session.state.getState("remote"), "remote_turn");
538
+ assert.equal(session.state.getState('local'), 'turn');
539
+ assert.equal(session.state.getState('remote'), 'remote_turn');
540
+ assert.equal(session.state.getHistory().length, 1);
541
+ assert.equal(session.state.getHistory()[0].player, 'remote');
542
+ assert.equal(session.state.getLastStart(), 'remote');
543
+ }
544
+
545
+ {
546
+ const { client, session } = await startGameWithFirstPlayer('local');
547
+ session.actions.move({ step: 'local-before-disconnect' });
548
+ await waitForBus();
549
+
550
+ assert.equal(session.state.getState('local'), 'remote_turn');
551
+ assert.equal(session.state.getState('remote'), 'turn');
552
+ assert.equal(session.state.getHistory().length, 1);
553
+
554
+ session.bus.dispatch({ type: 'OFFLINE', from: 'local' });
555
+ await waitForBus();
556
+
557
+ assert.equal(session.state.getState('local'), 'remote_turn');
558
+ assert.equal(session.state.getState('remote'), 'offline');
559
+
560
+ client.inbound({ type: 'SYNC_REQUEST', from: 'remote' });
561
+ await waitForBus();
562
+
563
+ const sent = client.sent.at(-1);
564
+ assert.equal(sent.type, 'SYNC_STATE');
565
+ assert.equal(sent.payload.resumeTurn, 'remote');
566
+ assert.equal(session.state.getState('local'), 'remote_turn');
567
+ assert.equal(session.state.getState('remote'), 'turn');
302
568
  assert.equal(session.state.getHistory().length, 1);
303
- assert.equal(session.state.getHistory()[0].player, "local");
304
- assert.equal(session.state.getLastStart(), "local");
569
+ assert.equal(session.state.getHistory()[0].player, 'local');
305
570
  }
306
571
 
307
- console.log("serialization smoke passed");
572
+ console.log('serialization smoke passed');