From 799379fd742e194581d804cea87a59030e92d47b Mon Sep 17 00:00:00 2001 From: Osamaali313 <86572800+Osamaali313@users.noreply.github.com> Date: Sun, 21 Jun 2026 23:28:58 +0300 Subject: [PATCH] Fix --no-open ignored on macOS and Windows in content-calendar viewer The auto-open guard combined `and`/`or` without parentheses: if not args.no_open and os.environ.get("DISPLAY", "") \ or sys.platform == "darwin" or sys.platform == "win32": `and` binds tighter than `or`, so this parses as `(not no_open and DISPLAY) or darwin or win32`. The two platform terms stand alone and ignore `args.no_open`, so on macOS and Windows the browser auto-opens even when the user passes --no-open (the documented "Don't auto-open the browser" flag). Linux happened to honor the flag, which hid the bug. Extract the decision into a small, testable `_should_auto_open(no_open, platform, display)` helper that short-circuits on no_open first. --- bin/notfair-content-calendar | 9 ++++- test/unit/test_content_calendar_auto_open.py | 38 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 test/unit/test_content_calendar_auto_open.py diff --git a/bin/notfair-content-calendar b/bin/notfair-content-calendar index ce33bd23..45eaad25 100755 --- a/bin/notfair-content-calendar +++ b/bin/notfair-content-calendar @@ -359,6 +359,13 @@ def _port_in_use(port: int, host: str = "127.0.0.1") -> bool: return False +def _should_auto_open(no_open: bool, platform: str, display: str) -> bool: + """Whether to auto-open the browser. Honors --no-open on every platform.""" + if no_open: + return False + return bool(display) or platform == "darwin" or platform == "win32" + + def main(argv=None) -> int: parser = argparse.ArgumentParser(prog="notfair-content-calendar") parser.add_argument("--port", type=int, default=DEFAULT_PORT) @@ -396,7 +403,7 @@ def main(argv=None) -> int: print(f"Reading: {calendar_path}") print("Ctrl+C to stop.") - if not args.no_open and os.environ.get("DISPLAY", "") or sys.platform == "darwin" or sys.platform == "win32": + if _should_auto_open(args.no_open, sys.platform, os.environ.get("DISPLAY", "")): # Open in a thread so we don't block startup if the browser command stalls. threading.Thread(target=lambda: (time.sleep(0.2), webbrowser.open(url)), daemon=True).start() diff --git a/test/unit/test_content_calendar_auto_open.py b/test/unit/test_content_calendar_auto_open.py new file mode 100644 index 00000000..7240dd2c --- /dev/null +++ b/test/unit/test_content_calendar_auto_open.py @@ -0,0 +1,38 @@ +import unittest +import importlib.util +from importlib.machinery import SourceFileLoader +import sys +from pathlib import Path + +# bin script has no .py extension, so give spec an explicit source loader. +script_path = Path(__file__).parent.parent.parent / 'bin' / 'notfair-content-calendar' +loader = SourceFileLoader("notfair_content_calendar", str(script_path)) +spec = importlib.util.spec_from_file_location("notfair_content_calendar", str(script_path), loader=loader) +mod = importlib.util.module_from_spec(spec) +sys.modules["notfair_content_calendar"] = mod +spec.loader.exec_module(mod) + +should_auto_open = mod._should_auto_open + + +class TestShouldAutoOpen(unittest.TestCase): + def test_no_open_suppresses_on_every_platform(self): + # --no-open must win on macOS and Windows too, not just headless Linux. + for platform in ("darwin", "win32", "linux"): + self.assertFalse( + should_auto_open(no_open=True, platform=platform, display=""), + f"--no-open should suppress auto-open on {platform}", + ) + self.assertFalse(should_auto_open(no_open=True, platform="linux", display=":0")) + + def test_default_opens_where_a_gui_exists(self): + self.assertTrue(should_auto_open(False, "darwin", "")) + self.assertTrue(should_auto_open(False, "win32", "")) + self.assertTrue(should_auto_open(False, "linux", ":0")) + + def test_headless_linux_does_not_open(self): + self.assertFalse(should_auto_open(False, "linux", "")) + + +if __name__ == "__main__": + unittest.main()