Skip to content

Commit e421288

Browse files
committed
ci: trim redundant tests & add topbar and scoreboard buttons
Signed-off-by: Dhruv Arora <dhruv.arora1@autodesk.com>
1 parent 2d2b184 commit e421288

8 files changed

Lines changed: 132 additions & 80 deletions

fission/src/test/PreferencesSystem.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe("Preferences System Global Values", () => {
3535
test("Setting without saving", () => {
3636
PreferencesSystem.setUserPreference("SceneRotationSensitivity", 13)
3737
PreferencesSystem.setUserPreference("RenderSceneTags", false)
38-
PreferencesSystem.setUserPreference("ShowViewCube", true)
38+
PreferencesSystem.setUserPreference("ShowViewCube", false)
3939

4040
window.localStorage.setItem("Preferences", "{}") // Clears local storage
4141
PreferencesSystem.loadPreferences()

fission/src/test/scene/DragModeSystem.test.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as THREE from "three"
22
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"
33
import { MiraType } from "@/mirabuf/MirabufLoader"
44
import MirabufSceneObject, { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject"
5-
import EventSystem, { type SynthesisEventListener } from "@/systems/EventSystem.ts"
5+
import EventSystem from "@/systems/EventSystem.ts"
66
import PhysicsSystem from "@/systems/physics/PhysicsSystem"
77
import { CameraMode, CustomTargetControls } from "@/systems/scene/CameraControls"
88
import DragModeSystem from "@/systems/scene/DragModeSystem"
@@ -98,33 +98,18 @@ describe("DragModeSystem Integration Tests", () => {
9898
EventSystem.dispatch("SetDragModeEvent", { enabled: false })
9999
expect(dragModeSystem.enabled).toBe(false)
100100
})
101-
102-
// The top bar button mirrors the same command it dispatches, so every caller has to go
103-
// through the event rather than assigning `enabled` directly or the button goes stale.
104-
test("the command reaches listeners alongside the system", () => {
105-
const listener = vi.fn<SynthesisEventListener<"SetDragModeEvent">>()
106-
const unlisten = EventSystem.listen("SetDragModeEvent", listener)
107-
108-
EventSystem.dispatch("SetDragModeEvent", { enabled: true })
109-
110-
expect(listener).toHaveBeenLastCalledWith(expect.objectContaining({ enabled: true }))
111-
expect(dragModeSystem.enabled).toBe(true)
112-
113-
unlisten()
114-
})
115101
})
116102

117103
describe("Cleanup", () => {
118-
test("should cleanup properly on destroy", () => {
119-
const removeEventListenerSpy = vi.spyOn(window, "removeEventListener")
104+
test("stops responding to the command once destroyed", () => {
105+
EventSystem.dispatch("SetDragModeEvent", { enabled: true })
120106

121-
dragModeSystem.enabled = true
122107
dragModeSystem.destroy()
123-
124108
expect(dragModeSystem.enabled).toBe(false)
125-
expect(removeEventListenerSpy).toHaveBeenCalledWith("SetDragModeEvent", expect.any(Function))
126109

127-
removeEventListenerSpy.mockRestore()
110+
EventSystem.dispatch("SetDragModeEvent", { enabled: true })
111+
112+
expect(dragModeSystem.enabled).toBe(false)
128113
})
129114
})
130115

fission/src/test/ui/CollapsibleGroup.test.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,28 @@ import type React from "react"
33
import { useRef } from "react"
44
import { describe, expect, test } from "vitest"
55
import { CollapsibleGroup, type CollapsibleItem } from "@/ui/components/topbar/CollapsibleGroup"
6+
import { TOP_BAR_GAP_PX } from "@/ui/components/topbar/TopBarConfig"
67
import { TopBarFitProvider } from "@/ui/components/topbar/TopBarFitProvider"
78

89
const ITEM_COUNT = 5
910

10-
const ITEMS: readonly CollapsibleItem[] = Array.from({ length: ITEM_COUNT }, (_, i) => ({
11-
key: `item-${i}`,
12-
node: <div data-testid="item" style={{ width: 40, height: 20 }} />,
13-
}))
11+
const makeItems = (count: number, width: number): CollapsibleItem[] =>
12+
Array.from({ length: count }, (_, i) => ({
13+
key: `w${width}-${i}`,
14+
node: <div data-testid="item" style={{ width, height: 20 }} />,
15+
}))
1416

15-
const Harness: React.FC<{ width: number }> = ({ width }) => {
17+
const ITEMS = makeItems(ITEM_COUNT, 40)
18+
19+
const Harness: React.FC<{ width: number; items?: readonly CollapsibleItem[] }> = ({ width, items = ITEMS }) => {
1620
const rowRef = useRef<HTMLDivElement>(null)
1721
const spacerRef = useRef<HTMLDivElement>(null)
1822

1923
return (
20-
<div ref={rowRef} style={{ display: "flex", alignItems: "center", gap: 12, width }}>
24+
<div ref={rowRef} style={{ display: "flex", alignItems: "center", gap: TOP_BAR_GAP_PX, width }}>
2125
<TopBarFitProvider rowRef={rowRef} spacerRef={spacerRef}>
2226
<CollapsibleGroup
23-
items={ITEMS}
27+
items={items}
2428
always={<div data-testid="always" style={{ width: 60, height: 20, flexShrink: 0 }} />}
2529
/>
2630
<div ref={spacerRef} style={{ flexGrow: 1 }} />
@@ -48,13 +52,22 @@ describe("CollapsibleGroup", () => {
4852

4953
await waitFor(() => expect(visibleItems(container)).toBe(ITEM_COUNT))
5054
expectNoOverflow(container)
51-
})
5255

53-
test("keeps the always slot even when every item has been shed", async () => {
54-
const { container } = render(<Harness width={80} />)
56+
rerender(<Harness width={80} />)
5557

5658
await waitFor(() => expect(visibleItems(container)).toBe(0))
5759
expect(container.querySelectorAll("[data-testid=always]")).toHaveLength(1)
5860
expectNoOverflow(container)
5961
})
62+
63+
test("re-measures when the item set changes instead of reusing stale widths", async () => {
64+
const { container, rerender } = render(<Harness width={400} items={makeItems(5, 40)} />)
65+
66+
await waitFor(() => expect(visibleItems(container)).toBe(5))
67+
68+
rerender(<Harness width={400} items={makeItems(5, 150)} />)
69+
70+
await waitFor(() => expect(visibleItems(container)).toBeLessThan(5))
71+
expectNoOverflow(container)
72+
})
6073
})

fission/src/test/ui/Scoreboard.test.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import { render, waitFor } from "@testing-library/react"
1+
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"
22
import { act } from "react"
3-
import { describe, expect, test } from "vitest"
3+
import { beforeEach, describe, expect, test } from "vitest"
44
import EventSystem from "@/systems/EventSystem"
55
import { MatchModeType } from "@/systems/match_mode/MatchModeTypes"
66
import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
77
import Scoreboard from "@/ui/components/Scoreboard"
8+
import GameplayControls from "@/ui/components/topbar/GameplayControls"
89

9-
const isShown = (container: HTMLElement) => container.textContent?.includes("RED") === true
10+
const isShown = (container: HTMLElement) => container.firstChild !== null
1011

1112
const setAlwaysShow = (value: boolean) => act(() => PreferencesSystem.setUserPreference("AlwaysShowScoreboard", value))
1213

1314
const setMatchMode = (mode: MatchModeType) => act(() => EventSystem.dispatch("MatchStateChangedEvent", { mode }))
1415

1516
describe("Scoreboard", () => {
17+
beforeEach(() => setAlwaysShow(false))
18+
1619
test("only follows match state when it is not set to always show", async () => {
17-
setAlwaysShow(false)
1820
const { container } = render(<Scoreboard />)
1921
expect(isShown(container)).toBe(false)
2022

@@ -34,3 +36,30 @@ describe("Scoreboard", () => {
3436
await waitFor(() => expect(isShown(container)).toBe(false))
3537
})
3638
})
39+
40+
describe("ScoreboardButton", () => {
41+
const button = () => within(screen.getByLabelText(/^Scoreboard:/)).getByRole("button")
42+
43+
beforeEach(() => setAlwaysShow(false))
44+
45+
test("reports the current mode and toggles the preference when clicked", async () => {
46+
render(<GameplayControls />)
47+
48+
expect(screen.getByLabelText("Scoreboard: Only During Matches")).toBeInTheDocument()
49+
expect(button()).toHaveAttribute("aria-pressed", "false")
50+
51+
fireEvent.click(button())
52+
53+
expect(PreferencesSystem.getUserPreference("AlwaysShowScoreboard")).toBe(true)
54+
await waitFor(() => expect(screen.getByLabelText("Scoreboard: Always On")).toBeInTheDocument())
55+
expect(button()).toHaveAttribute("aria-pressed", "true")
56+
})
57+
58+
test("follows the preference when it changes elsewhere", async () => {
59+
render(<GameplayControls />)
60+
61+
setAlwaysShow(true)
62+
63+
await waitFor(() => expect(button()).toHaveAttribute("aria-pressed", "true"))
64+
})
65+
})

fission/src/test/ui/SplitButtonDropdown.test.tsx

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,48 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"
22
import { describe, expect, test, vi } from "vitest"
33
import SplitButtonDropdown, { type SplitButtonMenuItem } from "@/ui/components/SplitButtonDropdown"
44

5-
const renderDropdown = (items: SplitButtonMenuItem[], onIconClick = vi.fn()) => {
6-
render(<SplitButtonDropdown icon={<span>icon</span>} onIconClick={onIconClick} items={items} />)
7-
return onIconClick
8-
}
9-
105
const openMenu = () => fireEvent.click(screen.getByLabelText("Open dropdown"))
116

12-
const expectMenuClosed = () => waitFor(() => expect(screen.queryByRole("menu")).toBeNull())
13-
147
describe("SplitButtonDropdown", () => {
15-
test("a click anywhere inside a row selects it and closes the menu", async () => {
8+
test("clicking a row's leading icon still selects the item and closes the menu", async () => {
169
const onSelect = vi.fn()
17-
renderDropdown([{ key: "only", label: "Always shown", icon: <span data-testid="leading-icon" />, onSelect }])
10+
const items: SplitButtonMenuItem[] = [
11+
{ key: "only", label: "Always shown", icon: <span data-testid="leading-icon" />, onSelect },
12+
]
13+
render(<SplitButtonDropdown icon={<span>icon</span>} onIconClick={vi.fn()} items={items} />)
1814

1915
openMenu()
2016
fireEvent.click(screen.getByTestId("leading-icon"))
2117

2218
expect(onSelect).toHaveBeenCalledTimes(1)
23-
await expectMenuClosed()
19+
await waitFor(() => expect(screen.queryByRole("menu")).toBeNull())
2420
})
2521

2622
test("the icon half runs its own action without opening the menu", () => {
27-
const onIconClick = renderDropdown([{ key: "only", label: "Always shown", onSelect: vi.fn() }])
23+
const onIconClick = vi.fn()
24+
const items: SplitButtonMenuItem[] = [{ key: "only", label: "Always shown", onSelect: vi.fn() }]
25+
render(<SplitButtonDropdown icon={<span>icon</span>} onIconClick={onIconClick} items={items} />)
2826

2927
fireEvent.click(screen.getByText("icon"))
3028

3129
expect(onIconClick).toHaveBeenCalledTimes(1)
3230
expect(screen.queryByRole("menu")).toBeNull()
3331
})
32+
33+
test("reserves the icon slot on every row once any row has an icon", () => {
34+
const items: SplitButtonMenuItem[] = [
35+
{ key: "with", label: "With icon", icon: <span data-testid="leading-icon" />, onSelect: vi.fn() },
36+
{ key: "without", label: "Without icon", onSelect: vi.fn() },
37+
]
38+
render(<SplitButtonDropdown icon={<span>icon</span>} onIconClick={vi.fn()} items={items} />)
39+
40+
openMenu()
41+
42+
const rows = screen.getAllByRole("menuitem")
43+
const slotWidth = (row: HTMLElement) => row.firstElementChild!.getBoundingClientRect().width
44+
45+
expect(rows).toHaveLength(2)
46+
expect(slotWidth(rows[1])).toBe(slotWidth(rows[0]))
47+
expect(slotWidth(rows[1])).toBeGreaterThan(0)
48+
})
3449
})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"
2+
import { act } from "react"
3+
import { describe, expect, test, vi } from "vitest"
4+
import EventSystem from "@/systems/EventSystem"
5+
import TopBar from "@/ui/components/TopBar"
6+
7+
const dragButton = () => within(screen.getByLabelText(/Drag Mode$/)).getByRole("button")
8+
9+
describe("TopBar drag mode", () => {
10+
test("mirrors the drag mode command in both directions", async () => {
11+
const listener = vi.fn()
12+
const unlisten = EventSystem.listen("SetDragModeEvent", listener)
13+
14+
render(<TopBar />)
15+
expect(dragButton()).toHaveAttribute("aria-pressed", "false")
16+
17+
act(() => EventSystem.dispatch("SetDragModeEvent", { enabled: true }))
18+
19+
await waitFor(() => expect(dragButton()).toHaveAttribute("aria-pressed", "true"))
20+
21+
fireEvent.click(dragButton())
22+
23+
expect(listener).toHaveBeenLastCalledWith(expect.objectContaining({ enabled: false }))
24+
await waitFor(() => expect(dragButton()).toHaveAttribute("aria-pressed", "false"))
25+
26+
unlisten()
27+
})
28+
})

fission/src/test/ui/TopBarFit.test.ts

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,27 @@ import { computeVisibleCount } from "@/ui/components/topbar/TopBarFit"
33

44
const GAP = 12
55
const BUTTON = 40
6-
const SLACK = 8
6+
const SUBPIXEL_SLACK_PX = 8
77

88
const BUTTONS = Array<number>(6).fill(BUTTON)
99

10-
const budgetFor = (n: number) => n * (BUTTON + GAP) + SLACK
10+
const leastBudgetFitting = (n: number) => n * (BUTTON + GAP) + SUBPIXEL_SLACK_PX
1111

1212
describe("computeVisibleCount", () => {
13-
test("shows everything when there is room to spare", () => {
14-
expect(computeVisibleCount(budgetFor(6) + 100, BUTTONS, GAP)).toBe(6)
15-
})
16-
17-
test("shows everything at exactly the budget it needs", () => {
18-
expect(computeVisibleCount(budgetFor(6), BUTTONS, GAP)).toBe(6)
19-
})
20-
21-
test("holds back slack so a sub-pixel measurement error cannot overflow the row", () => {
22-
expect(computeVisibleCount(budgetFor(6) - 1, BUTTONS, GAP)).toBe(5)
23-
expect(computeVisibleCount(budgetFor(6) - SLACK, BUTTONS, GAP)).toBe(5)
24-
})
25-
26-
test("drops one item at a time as the budget shrinks", () => {
27-
for (let visible = 0; visible <= 6; visible++) {
28-
expect(computeVisibleCount(budgetFor(visible), BUTTONS, GAP)).toBe(visible)
29-
expect(computeVisibleCount(budgetFor(visible + 1) - 1, BUTTONS, GAP)).toBe(visible)
13+
test("drops exactly one item per item-width of budget, holding back slack", () => {
14+
for (let visible = 0; visible <= BUTTONS.length; visible++) {
15+
expect(computeVisibleCount(leastBudgetFitting(visible), BUTTONS, GAP)).toBe(visible)
16+
expect(computeVisibleCount(leastBudgetFitting(visible + 1) - 1, BUTTONS, GAP)).toBe(visible)
3017
}
3118
})
3219

33-
test("drops from the end, so the front of the list is the last to go", () => {
34-
const widths = [BUTTON, BUTTON, 200]
35-
36-
expect(computeVisibleCount(budgetFor(2) + 200 + GAP, widths, GAP)).toBe(3)
37-
expect(computeVisibleCount(budgetFor(2), widths, GAP)).toBe(2)
38-
expect(computeVisibleCount(budgetFor(1), widths, GAP)).toBe(1)
39-
})
40-
4120
test("stops at an item too wide to fit rather than skipping past it", () => {
42-
expect(computeVisibleCount(budgetFor(3), [200, BUTTON, BUTTON, BUTTON], GAP)).toBe(0)
21+
expect(computeVisibleCount(leastBudgetFitting(3), [200, BUTTON, BUTTON, BUTTON], GAP)).toBe(0)
4322
})
4423

45-
test("shows nothing when the budget is gone or negative", () => {
24+
test("shows nothing for an exhausted budget or an empty list", () => {
4625
expect(computeVisibleCount(0, BUTTONS, GAP)).toBe(0)
4726
expect(computeVisibleCount(-500, BUTTONS, GAP)).toBe(0)
48-
})
49-
50-
test("handles an empty item list", () => {
5127
expect(computeVisibleCount(1000, [], GAP)).toBe(0)
5228
})
5329
})

fission/src/test/ui/TopBarIcons.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { describe, expect, test } from "vitest"
33
import { TOP_BAR_ICON_NAMES, TopBarIcon } from "@/ui/components/topbar/TopBarIcons"
44

55
describe("TopBarIcon", () => {
6-
test("every declared icon name resolves to an svg file", () => {
6+
test("every icon name maps to an svg on disk, and every svg on disk is named", () => {
7+
const onDisk = Object.keys(import.meta.glob("../../ui/components/topbar/icons/*.svg")).map(path =>
8+
path.replace(/^.*\//, "").replace(/\.svg$/, "")
9+
)
10+
11+
expect([...TOP_BAR_ICON_NAMES].sort()).toEqual(onDisk.sort())
12+
713
for (const name of TOP_BAR_ICON_NAMES) {
814
const { container } = render(<TopBarIcon name={name} />)
915

0 commit comments

Comments
 (0)