Skip to content

Commit eb1ccad

Browse files
committed
Fix tests
Also refresh subscription in ArrangementViewer when a new song is loaded.
1 parent 43afb8b commit eb1ccad

File tree

4 files changed

+51
-237
lines changed

4 files changed

+51
-237
lines changed

src/components/ui/Arrangement/ArrangementPlayControls.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class ArrangementPlayControls extends UIComponent<IArrangementControlsTop
7474
imageOnly
7575
id="playbackButton"
7676
onClick={() => {
77-
arrangementPlayer.play();
77+
void arrangementPlayer.play();
7878
}}>
7979
<Icon src={Codicon.DebugStart} />
8080
</Button>

src/components/ui/Arrangement/ArrangementViewer.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,37 @@ export class ArrangementViewer extends UIComponent<IArrangementViewerProps, IArr
8181
this.resizeObserver.observe(this.viewerRef.current!);
8282

8383
const arrangement = arrangementPlayer.arrangementView;
84-
this.addSubscription(arrangement.timeParams, this.timeParamsSubscription);
84+
this.addSubscription(arrangement.timeParams, this.timeParamsSubscription, true);
8585

8686
// If desired, turn on auto-follow like so.
8787
if (autoFollowIsOn) {
8888
arrangementPlayer.animationEngine.connect(this.autoFollow);
89-
} else {
90-
// Otherwise, set up the subscription which will turn it on again.
91-
this.addSubscription(arrangementPlayer.animationEngine, this.animationEngineSubscription);
9289
}
9390

91+
// Otherwise, set up the subscription which will turn it on again.
92+
this.addSubscription(arrangementPlayer.animationEngine, this.animationEngineSubscription, true);
93+
9494
this.updateScrollShadows();
9595
}
9696

9797
public override componentDidUpdate(prevProps: IArrangementViewerProps, prevState: IArrangementViewerState): void {
9898
super.componentDidUpdate(prevProps, prevState);
99+
100+
const { arrangementPlayer } = this.props;
101+
const { autoFollowIsOn } = this.state;
102+
103+
if (prevProps.arrangementPlayer !== arrangementPlayer) {
104+
prevProps.arrangementPlayer.animationEngine.disconnect(this.autoFollow);
105+
106+
const arrangement = arrangementPlayer.arrangementView;
107+
this.addSubscription(arrangement.timeParams, this.timeParamsSubscription);
108+
109+
if (autoFollowIsOn) {
110+
arrangementPlayer.animationEngine.connect(this.autoFollow);
111+
}
112+
this.addSubscription(arrangementPlayer.animationEngine, this.animationEngineSubscription);
113+
}
114+
99115
this.updateScrollShadows();
100116
}
101117

tests/player/ArrangementPlayer.spec.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,32 @@
55

66
import { describe, expect, it, vi } from "vitest";
77

8-
import type {
9-
INoteStyle, IPolyrhythm, ITimeParamsView, Mutable
10-
} from "../../src/core/types/general.js";
11-
import type {
12-
ICallbackEvent, IInterval, ILoopInterval, ITimeCoordinator, ITrackPlayer
13-
} from "../../src/player/types.js";
8+
import type { INoteStyle, IPolyrhythm, ITimeParams, Mutable } from "../../src/core/types/general.js";
9+
import type { ICallbackEvent, IInterval, ILoopInterval, } from "../../src/player/types.js";
1410

1511
// Simple subscribable helper with publish capability for tests
1612
type Sub = (...args: unknown[]) => void;
1713

1814
type CallbackHelper = { callback: () => void; } & { realTime: number; } & { identifier: unknown; };
1915

20-
interface PublishableSubscribable { subscribe: (cb: Sub) => void; unsubscribe: (cb: Sub) => void; publish: () => void; }
16+
interface PublishableSubscribable {
17+
subscribe: (cb: Sub) => () => void; unsubscribe: (cb: Sub) => void;
18+
publish: () => void;
19+
}
20+
2121
const makeSubscribable = (): PublishableSubscribable => {
2222
const subs: Sub[] = [];
2323

2424
return {
2525
subscribe: (cb: Sub) => {
2626
subs.push(cb);
27+
28+
return () => {
29+
const i = subs.indexOf(cb);
30+
if (i !== -1) {
31+
subs.splice(i, 1);
32+
}
33+
};
2734
},
2835
unsubscribe: (cb: Sub) => {
2936
const i = subs.indexOf(cb);
@@ -41,11 +48,11 @@ const makeSubscribable = (): PublishableSubscribable => {
4148

4249
// Mock TimeCoordinator used by ArrangementPlayer
4350
vi.mock("../../src/player/TimeCoordinator.js", () => {
44-
class MockTimeCoordinator implements ITimeCoordinator {
51+
class MockTimeCoordinator {
4552
public realTimeLength: RealTime;
4653
private readonly subs = makeSubscribable();
4754

48-
public constructor(_timeParams: ITimeParamsView) {
55+
public constructor(_timeParams: ITimeParams) {
4956
this.realTimeLength = 1;
5057
}
5158

@@ -92,12 +99,12 @@ vi.mock("../../src/player/TimeCoordinator.js", () => {
9299

93100
// Mock TrackPlayer used inside ArrangementPlayer
94101
vi.mock("../../src/player/TrackPlayer.js", () => {
95-
class MockTrackPlayer implements ITrackPlayer {
102+
class MockTrackPlayer {
96103
public soloMute: null | "solo" | "mute" = null;
97104
public readonly currentPolyrhythmNotePublisher = makeSubscribable();
98105
public stopped = false;
99106
private readonly subs = makeSubscribable();
100-
public constructor(public track: ISbDmTrack, _tc: ITimeCoordinator) { }
107+
public constructor(public track: ISbDmTrack, _tc: TimeCoordinator) { }
101108

102109
public get currentPolyrhythmNote(): ISbDmNote | null {
103110
return null;
@@ -114,7 +121,7 @@ vi.mock("../../src/player/TrackPlayer.js", () => {
114121
}
115122
});
116123

117-
return events as unknown as ReturnType<ITrackPlayer["getEvents"]>;
124+
return events;
118125
}
119126

120127
public subscribe(cb: Sub): void {
@@ -159,7 +166,7 @@ const makeNote = (track: ISbDmTrack, timing: ITiming, noteStyle?: INoteStyle,
159166
const makeArrangement = (trackCount: number): ISbDmArrangement & { _publish: () => void; } => {
160167
const arrangementSubs = makeSubscribable();
161168
const timeParamsSubs = makeSubscribable();
162-
const timeParams: ITimeParamsView & { _publish: () => void; } = {
169+
const timeParams: ITimeParams & { _publish: () => void; } = {
163170
timeSignature: "4/4",
164171
tempo: 120,
165172
length: 1,
@@ -202,7 +209,7 @@ const makeArrangement = (trackCount: number): ISbDmArrangement & { _publish: ()
202209
id: i,
203210
filePath: `path/to/image${i}.png`,
204211
},
205-
colourGroup: "blue",
212+
color: "blue",
206213
noteStyles: {},
207214
...makeSubscribable()
208215
};
@@ -265,6 +272,8 @@ import {
265272
} from "../../src/core/ScoreBookDataModel.js";
266273
import { ArrangementPlayer } from "../../src/player/ArrangementPlayer.js";
267274
import { getNewId } from "../../src/core/utils.js";
275+
import type { TimeCoordinator } from "../../src/player/TimeCoordinator.js";
276+
import type { TrackPlayer } from "../../src/player/TrackPlayer.js";
268277

269278
describe("ArrangementPlayer", () => {
270279
it("creates track players and computes audible set (no solo)", () => {
@@ -278,7 +287,7 @@ describe("ArrangementPlayer", () => {
278287
it("audible set reacts to solo/mute changes via subscriptions", () => {
279288
const arrangement = makeArrangement(3);
280289
const player = new ArrangementPlayer(arrangement);
281-
const tps = Array.from(player.trackPlayers.values()) as Array<ITrackPlayer & { publish: () => void; }>;
290+
const tps = Array.from(player.trackPlayers.values()) as Array<TrackPlayer & { publish: () => void; }>;
282291

283292
// Solo the second track
284293
tps[1].soloMute = "solo";
@@ -331,7 +340,8 @@ describe("ArrangementPlayer", () => {
331340
const arrangement = makeArrangement(1);
332341
const player = new ArrangementPlayer(arrangement);
333342

334-
// Interval crosses loop boundary (length=1): [0.9, 1.2]
343+
// Interval crosses loop boundary (length=1): [0.9, 1.2].
344+
// @ts-expect-error Accessing internal for test purposes
335345
const events = player.getEvents({ start: 0.9, end: 1.2 });
336346
expect(events.length).toBeGreaterThan(0);
337347
// Ensure sorted order
@@ -344,7 +354,7 @@ describe("ArrangementPlayer", () => {
344354
return ("callback" in e) && ("identifier" in e);
345355
});
346356
const currentTimingUpdates: number[] = [];
347-
player.currentTimingPublisher.subscribe(() => {
357+
player.subscribe(() => {
348358
currentTimingUpdates.push(1);
349359
});
350360
cb?.callback();
@@ -356,7 +366,8 @@ describe("ArrangementPlayer", () => {
356366
const arrangement = makeArrangement(2);
357367
const player = new ArrangementPlayer(arrangement);
358368

359-
// Prime currentTiming by firing a timing callback
369+
// Prime currentTiming by firing a timing callback.
370+
// @ts-expect-error Accessing internal for test purposes
360371
const events = player.getEvents({ start: 0, end: 0.5 });
361372
const timingCb = events.find((e): e is ICallbackEvent & { identifier: unknown; } => {
362373
return ("callback" in e) && ("identifier" in e);
@@ -368,7 +379,7 @@ describe("ArrangementPlayer", () => {
368379
player.onStop();
369380
expect(player.currentTiming).toBeNull();
370381

371-
const tps = Array.from(player.trackPlayers.values()) as Array<ITrackPlayer & { stopped: boolean; }>;
382+
const tps = Array.from(player.trackPlayers.values()) as Array<TrackPlayer & { stopped: boolean; }>;
372383
tps.forEach((tp) => {
373384
expect(tp.stopped).toBe(true);
374385
});

0 commit comments

Comments
 (0)