Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions e2e/dashboard-layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,30 @@ test.describe("FanGuard dashboard Liquid Glass layout", () => {
await page.setViewportSize({ width: 960, height: 486 });
await page.goto("/");

const restoreButton = page.getByRole("button", { name: "Restore Auto" });
const applyButton = page.getByRole("button", { name: "Apply" });
const autoButton = page.getByRole("button", { name: "Auto" });
const manualButton = page.getByRole("button", { name: "Manual" });
const targetSlider = page.getByRole("slider", { name: "Target RPM" });
const telemetryHeader = page.getByText("Name", { exact: true });
const restoreBounds = await restoreButton.boundingBox();
const applyBounds = await applyButton.boundingBox();
const manualBounds = await manualButton.boundingBox();
const sliderBounds = await targetSlider.boundingBox();

expect(restoreBounds).not.toBeNull();
expect((restoreBounds?.x ?? 960) + (restoreBounds?.width ?? 1)).toBeLessThanOrEqual(960);
await expect(restoreButton).toBeInViewport();
expect(manualBounds).not.toBeNull();
expect(
(manualBounds?.x ?? 960) + (manualBounds?.width ?? 1),
).toBeLessThanOrEqual(960);
await expect(autoButton).toBeInViewport();
await expect(manualButton).toBeInViewport();
await expect(targetSlider).toBeInViewport();
await expect(
page.locator('[data-rpm-tick="major"]').first(),
).toBeInViewport();
await expect(telemetryHeader).toBeInViewport();
await expect(page.getByText("System Fan 1", { exact: true })).toBeInViewport();
expect((sliderBounds?.x ?? 0) + (sliderBounds?.width ?? 960)).toBeLessThanOrEqual(
applyBounds?.x ?? 0,
);
await expect(
page.getByText("System Fan 1", { exact: true }),
).toBeInViewport();
expect(
(sliderBounds?.x ?? 0) + (sliderBounds?.width ?? 960),
).toBeLessThanOrEqual(960);
expect(
await page.evaluate(() => document.documentElement.scrollWidth),
).toBeLessThanOrEqual(960);
Expand All @@ -37,18 +45,26 @@ test.describe("FanGuard dashboard Liquid Glass layout", () => {

await expect(page.getByRole("heading", { name: "FanGuard" })).toBeVisible();
await expect(page.getByText("Fan Control", { exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "More actions" })).toBeVisible();
await expect(page.getByRole("searchbox", { name: "Search sensors" })).toBeVisible();
await expect(
page.getByRole("button", { name: "More actions" }),
).toBeVisible();
await expect(
page.getByRole("searchbox", { name: "Search sensors" }),
).toBeVisible();

await expect(page.getByRole("button", { name: /^Fans\b/ })).toBeVisible();
await expect(
page.getByRole("button", { name: /^Temperature Sensors/ }),
).toBeVisible();
await expect(page.getByRole("button", { name: /^Disk Drives/ })).toBeVisible();
await expect(
page.getByRole("button", { name: /^Disk Drives/ }),
).toBeVisible();

await expect(page.getByRole("button", { name: /Back/i })).toHaveCount(0);
await expect(page.getByRole("button", { name: /Forward/i })).toHaveCount(0);
await expect(page.getByRole("button", { name: /to custom mode$/i })).toHaveCount(0);
await expect(
page.getByRole("button", { name: /to custom mode$/i }),
).toHaveCount(0);
await expect(page.getByRole("dialog")).toHaveCount(0);

await expect(page.getByText("SMC Connected")).toBeVisible();
Expand All @@ -66,9 +82,7 @@ test.describe("FanGuard dashboard Liquid Glass layout", () => {

await temperatureGroup.click();
await expect(temperatureGroup).toHaveAttribute("aria-expanded", "false");
await expect(
page.getByText("Battery", { exact: true }),
).not.toBeVisible();
await expect(page.getByText("Battery", { exact: true })).not.toBeVisible();
});

test("filters visible telemetry rows without changing global control", async ({
Expand Down
35 changes: 26 additions & 9 deletions e2e/fan-control.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { expect, test } from "@playwright/test";

test.describe("FanGuard global fan control", () => {
test("applies one manual RPM target to all detected fans", async ({ page }) => {
test("applies one manual RPM target to all detected fans", async ({
page,
}) => {
await page.goto("/");

await expect(page.getByText("1200", { exact: true })).toBeVisible();
Expand All @@ -10,10 +12,12 @@ test.describe("FanGuard global fan control", () => {
await page.getByRole("button", { name: "Manual" }).click();
await page.getByLabel("Target RPM").fill("5000");
await expect(page.getByText("5000 RPM", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Apply" }).click();
await expect(page.getByText(/Applying in [\d.]+s/)).toBeVisible();

await expect(page.getByRole("alert")).toHaveCount(0);
await expect(page.getByText("Applied 5000 RPM to 3 fans.")).toBeVisible();
await expect(page.getByText("Applied 5000 RPM to 3 fans.")).toBeVisible({
timeout: 5000,
});
await expect(page.getByText("System Fan 1")).toBeVisible();
await expect(page.getByText("System Fan 2")).toBeVisible();
await expect(page.getByText("System Fan 3")).toBeVisible();
Expand All @@ -25,18 +29,31 @@ test.describe("FanGuard global fan control", () => {

await page.getByRole("button", { name: "Manual" }).click();
await page.getByLabel("Target RPM").fill("5000");
await page.getByRole("button", { name: "Apply" }).click();
await page.getByRole("button", { name: "Restore Auto" }).click();
await expect(page.getByText("Applied 5000 RPM to 3 fans.")).toBeVisible({
timeout: 5000,
});
await page.getByRole("button", { name: "Auto" }).click();

await expect(page.getByText("Restored automatic control for 3 fans.")).toBeVisible();
await expect(
page.locator('[role="row"]').filter({ hasText: "System Fan 1" }).getByText("Auto", { exact: true }),
page.getByText("Restored automatic control for 3 fans."),
).toBeVisible();
await expect(
page.locator('[role="row"]').filter({ hasText: "System Fan 2" }).getByText("Auto", { exact: true }),
page
.locator('[role="row"]')
.filter({ hasText: "System Fan 1" })
.getByText("Auto", { exact: true }),
).toBeVisible();
await expect(
page.locator('[role="row"]').filter({ hasText: "System Fan 3" }).getByText("Auto", { exact: true }),
page
.locator('[role="row"]')
.filter({ hasText: "System Fan 2" })
.getByText("Auto", { exact: true }),
).toBeVisible();
await expect(
page
.locator('[role="row"]')
.filter({ hasText: "System Fan 3" })
.getByText("Auto", { exact: true }),
).toBeVisible();
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fanguard",
"version": "0.1.0-beta.5",
"version": "0.1.0-beta.6",
"private": true,
"description": "FanGuard — macOS fan control application built with Tauri v2 and Svelte 5",
"author": "naufaldi",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fanguard"
version = "0.1.0-beta.5"
version = "0.1.0-beta.6"
description = "FanGuard — macOS fan control utility: monitor temperatures and manage fan speeds via SMC"
authors = ["naufaldi"]
license = "MIT"
Expand Down
19 changes: 13 additions & 6 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,16 +293,21 @@ pub fn set_fan_auto(app_handle: AppHandle, fan_index: u8) -> Result<(), String>
}

#[tauri::command]
pub fn set_all_fans_constant_rpm(
state: State<'_, AppState>,
rpm: f32,
) -> Result<(), String> {
pub fn set_all_fans_constant_rpm(state: State<'_, AppState>, rpm: f32) -> Result<(), String> {
let writer_guard = state.smc_writer.lock().map_err(|error| error.to_string())?;
let writer = writer_guard.as_deref().ok_or_else(|| {
"Fan control unavailable — install or reconnect the privileged helper".to_string()
})?;
let mut service = SensorService::new();
let sensor_data = service.read_all_sensors().map_err(|error| error.to_string())?;
let sensor_data = service
.read_all_sensors()
.map_err(|error| error.to_string())?;
crate::fan_control::validate_manual_thermal_target(
rpm,
&sensor_data.fans,
&sensor_data.details,
)
.map_err(|error| error.to_string())?;

state
.fan_control
Expand All @@ -319,7 +324,9 @@ pub fn restore_all_fans_auto(state: State<'_, AppState>) -> Result<(), String> {
"Fan control unavailable — install or reconnect the privileged helper".to_string()
})?;
let mut service = SensorService::new();
let sensor_data = service.read_all_sensors().map_err(|error| error.to_string())?;
let sensor_data = service
.read_all_sensors()
.map_err(|error| error.to_string())?;

state
.fan_control
Expand Down
Loading
Loading