-
-
Notifications
You must be signed in to change notification settings - Fork 1
Animate the frog's mouth and menu-window critter from live mic level #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
1d53f70
Make the frog answer your voice in the menu window, and open its mouth
jamditis c855e32
Gate the overlay mouth on prefers-reduced-motion
jamditis a04ad13
Snap to exact rest on the event path, not only on release
jamditis 28d6c23
Subscribe to reduce-motion on both MediaQueryList APIs
jamditis fb45a8f
fix: Gate mic-level events to active sessions
jamditis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import { describe, expect, it } from "bun:test"; | ||
| import { onMediaQueryChange } from "./useMicLevel"; | ||
|
|
||
| // The reduce-motion subscription has to work on both MediaQueryList APIs, because | ||
| // tauri.conf.json still ships a 10.15 minimum and Catalina's WKWebView implements | ||
| // matchMedia without addEventListener. That branch cannot execute in a modern | ||
| // engine, so the only way to prove it is to hand the function a query object | ||
| // shaped like the old one. | ||
|
|
||
| type Handler = (event: MediaQueryListEvent) => void; | ||
|
|
||
| interface FakeQuery { | ||
| /** The MediaQueryList stand-in, with only one of the two listener APIs. */ | ||
| query: MediaQueryList; | ||
| /** Listener calls in order, so a silent no-op cannot pass. */ | ||
| calls: string[]; | ||
| /** Fire a change the way the browser would, or throw if nothing is listening. */ | ||
| emit: (matches: boolean) => void; | ||
| isSubscribed: () => boolean; | ||
| } | ||
|
|
||
| /** A Safari 14+ MediaQueryList: the modern listener API only. */ | ||
| function modernQuery(): FakeQuery { | ||
| const calls: string[] = []; | ||
| let registered: Handler | null = null; | ||
| const query = { | ||
| matches: false, | ||
| addEventListener(type: string, handler: Handler) { | ||
| calls.push(`add:${type}`); | ||
| registered = handler; | ||
| }, | ||
| removeEventListener(type: string, handler: Handler) { | ||
| calls.push(`remove:${type}`); | ||
| if (registered === handler) registered = null; | ||
| }, | ||
| }; | ||
| return { | ||
| query: query as unknown as MediaQueryList, | ||
| calls, | ||
| emit: (matches) => { | ||
| if (!registered) throw new Error("no listener registered"); | ||
| registered({ matches } as MediaQueryListEvent); | ||
| }, | ||
| isSubscribed: () => registered !== null, | ||
| }; | ||
| } | ||
|
|
||
| /** A Catalina-era MediaQueryList: addListener/removeListener and nothing else. */ | ||
| function legacyQuery(): FakeQuery { | ||
| const calls: string[] = []; | ||
| let registered: Handler | null = null; | ||
| const query = { | ||
| matches: false, | ||
| addListener(handler: Handler) { | ||
| calls.push("addListener"); | ||
| registered = handler; | ||
| }, | ||
| removeListener(handler: Handler) { | ||
| calls.push("removeListener"); | ||
| if (registered === handler) registered = null; | ||
| }, | ||
| }; | ||
| return { | ||
| query: query as unknown as MediaQueryList, | ||
| calls, | ||
| emit: (matches) => { | ||
| if (!registered) throw new Error("no listener registered"); | ||
| registered({ matches } as MediaQueryListEvent); | ||
| }, | ||
| isSubscribed: () => registered !== null, | ||
| }; | ||
| } | ||
|
|
||
| const noop: Handler = () => {}; | ||
|
|
||
| describe("onMediaQueryChange", () => { | ||
| it("uses the modern listener API when it is available", () => { | ||
| const { query, calls, isSubscribed } = modernQuery(); | ||
| const unsubscribe = onMediaQueryChange(query, noop); | ||
| expect(calls).toEqual(["add:change"]); | ||
| expect(isSubscribed()).toBe(true); | ||
| unsubscribe(); | ||
| expect(calls).toEqual(["add:change", "remove:change"]); | ||
| expect(isSubscribed()).toBe(false); | ||
| }); | ||
|
|
||
| it("falls back to addListener rather than throwing on an old WKWebView", () => { | ||
| // The regression this exists for: addEventListener is absent there, so calling | ||
| // it throws during mount, and because the hook mounts in both LiveFrog and | ||
| // RecordingOverlay the throw takes the whole UI down over an animation | ||
| // preference. | ||
| const { query, calls, isSubscribed } = legacyQuery(); | ||
| const unsubscribe = onMediaQueryChange(query, noop); | ||
| expect(calls).toEqual(["addListener"]); | ||
| expect(isSubscribed()).toBe(true); | ||
| unsubscribe(); | ||
| expect(calls).toEqual(["addListener", "removeListener"]); | ||
| expect(isSubscribed()).toBe(false); | ||
| }); | ||
|
|
||
| it("delivers changes on both APIs, and stops after unsubscribe", () => { | ||
| // Registering without receiving would be a silent no-op — on the legacy path | ||
| // just as broken as the throw, only quieter. | ||
| for (const build of [modernQuery, legacyQuery]) { | ||
| const fake = build(); | ||
| const seen: boolean[] = []; | ||
| const unsubscribe = onMediaQueryChange(fake.query, (event) => | ||
| seen.push(event.matches), | ||
| ); | ||
|
|
||
| fake.emit(true); | ||
| fake.emit(false); | ||
| expect(seen).toEqual([true, false]); | ||
|
|
||
| unsubscribe(); | ||
| expect(() => fake.emit(true)).toThrow("no listener registered"); | ||
| expect(seen).toEqual([true, false]); | ||
| } | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.