diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..3f538f5 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,8 @@ +;; TODO: We have to be careful what variables we set here. Some can cause the +;; Eldev linters to not read the settings here. See emacs-eldev/eldev#83. +((nil + ;; FIXME: This is just set to silence linter line-length warnings. It should + ;; be set to an intentional value, then the long-lines fixed. + (fill-column . 136) + (indent-tabs-mode . nil) + (sentence-end-double-space . nil))) diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..bdc4c7a --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +direnv_layout_dir="$PWD/.cache/direnv" +use flake diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..3c7598b --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:base"], + "lockFileMaintenance": { + "enabled":true + }, + "nix": { + "enabled":true + } +} diff --git a/.gitignore b/.gitignore index ccc9fd9..5076318 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -*.DS_Store \ No newline at end of file +*.elc +/.cache/direnv/ +/dist +/.eldev +/result diff --git a/Eldev b/Eldev new file mode 100644 index 0000000..6fc3a32 --- /dev/null +++ b/Eldev @@ -0,0 +1,30 @@ +;;; Eldev --- Build configuration -*- mode: emacs-lisp; lexical-binding: t; -*- + +(require 'eldev) +(require 'elisp-lint) + +(define-error 'auto-dark-test-invalid-test-selection "Unknown test type") + +(eldev-add-loading-roots 'test "initialize") + +(eldev-defoption auto-dark-test-selection (type) + "Select tests to run; type can be `main' or `integration'" + :options (-T --test-type) + :for-command test + :value TYPE + :default-value 'main + (pcase (intern type) + ('main + (setf eldev-test-fileset + `(:and ,eldev-test-fileset (:not "./tests/initialization")))) + ('integration (setf eldev-test-fileset "./tests/initialization")) + (_ (signal 'auto-dark-test-invalid-test-selection (list type))))) + +(setq + ;; run all linters by default + eldev-lint-default t + ;; ignore lisp files in the example directory + eldev-standard-excludes `(:or ,eldev-standard-excludes "./example") + ;; and disable the ‘elisp-lint’ validators that are already covered by + ;; ‘eldev-lint’ (see ‘eldev-linter-elisp’). + elisp-lint-ignored-validators '("checkdoc" "package-lint")) diff --git a/README.md b/README.md index be8920a..b4be0b4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ [![MELPA](https://melpa.org/packages/auto-dark-badge.svg)](https://melpa.org/#/auto-dark) +[![MELPA Stable](https://stable.melpa.org/packages/auto-dark-badge.svg)](https://stable.melpa.org/#/auto-dark) +[![built with garnix](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgarnix.io%2Fapi%2Fbadges%2FLionyxML%2Fauto-dark-emacs%3Fbranch%3Ddevelopment)](https://garnix.io/repo/LionyxML/auto-dark-emacs)(dev) + +[![built with garnix](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgarnix.io%2Fapi%2Fbadges%2FLionyxML%2Fauto-dark-emacs)](https://garnix.io/repo/LionyxML/auto-dark-emacs)(master) Do you want Emacs to follow your MacOS/Linux/Windows/Android dark-mode on/off options? @@ -28,7 +32,7 @@ Install it from [MELPA](https://melpa.org/#/auto-dark) and add to your ```emacs-lisp (require 'auto-dark) -(auto-dark-mode t) +(auto-dark-mode) ``` @@ -40,7 +44,7 @@ and then add the following to your `.emacs`: ```emacs-lisp (add-to-list 'load-path "~/.emacs.d/auto-dark/") (require 'auto-dark) -(auto-dark-mode t) +(auto-dark-mode) ``` Or use `use-package` to install: @@ -48,7 +52,7 @@ Or use `use-package` to install: ```emacs-lisp (use-package auto-dark - :config (auto-dark-mode t)) + :init (auto-dark-mode)) ``` @@ -61,7 +65,7 @@ If you use Spacemacs, add `(auto-dark)` to the ```emacs-lisp (use-package auto-dark - :init (spacemacs/defer-until-after-user-config (lambda () (auto-dark-mode t))) + :init (spacemacs/defer-until-after-user-config #'auto-dark-mode) :defer t) ``` @@ -72,8 +76,15 @@ built-in theme loading logic. ### Doom Emacs If you're under Doom Emacs, the following configuration should be -enough: +enough[^1]: +[^1]: There is possibly an issue with Doom Emacs’ initialization, where the +“Loading a theme can run Lisp code. Really load?” prompt can cause +initialization to fail. Auto-Dark attempts to ensure that this prompt doesn’t +occur during initialization, but it isn’t perfect. If this causes a problem for +you, try setting `custom-safe-themes` to `t` in your config before setting +`auto-dark-themes`. But be aware that this is a potential security concern. See +the documentation of `custom-safe-themes` for more details. ```emacs-lisp ;; In your packages.el @@ -83,16 +94,15 @@ enough: (after! doom-ui ;; set your favorite themes - (setq! auto-dark-dark-theme 'doom-one - auto-dark-light-theme 'doom-one-light) - (auto-dark-mode 1)) + (setq! auto-dark-themes '((doom-one) (doom-one-light))) + (auto-dark-mode)) ``` ## Notes for MacOS users From the box, this package takes advantage of some built-in functionality found -on the formulaes [Emacs Plus](https://github.com/d12frosted/homebrew-emacs-plus) +on the formulaes [Emacs Plus](https://github.com/d12frosted/homebrew-emacs-plus) and [Emacs Mac](https://github.com/railwaycat/homebrew-emacsmacport?tab=readme-ov-file) to make detecting switches faster. @@ -116,7 +126,7 @@ by going to: ``` -Settings -> Privacy & Security -> Emacs -> System Events +Settings -> Privacy & Security -> Emacs -> System Events ``` @@ -142,47 +152,50 @@ Following, a complete configuration with all settings set to its defaults: ```emacs-lisp (use-package auto-dark :ensure t - :config - (setq auto-dark-dark-theme 'wombat) - (setq auto-dark-light-theme 'leuven) - (setq auto-dark-polling-interval-seconds 5) - (setq auto-dark-allow-osascript nil) - (setq auto-dark-allow-powershell nil) - ;; (setq auto-dark-detection-method nil) ;; dangerous to be set manually - - (add-hook 'auto-dark-dark-mode-hook - (lambda () - ;; something to execute when dark mode is detected)) - - (add-hook 'auto-dark-light-mode-hook - (lambda () - ;; something to execute when light mode is detected)) - - (auto-dark-mode t)) + :custom + (auto-dark-themes '((wombat) (leuven))) + (auto-dark-polling-interval-seconds 5) + (auto-dark-allow-osascript nil) + (auto-dark-allow-powershell nil) + ;; (auto-dark-detection-method nil) ;; dangerous to be set manually + :hook + (auto-dark-dark-mode + . (lambda () + ;; something to execute when dark mode is detected + )) + (auto-dark-light-mode + . (lambda () + ;; something to execute when light mode is detected + )) + :init (auto-dark-mode)) ``` A short description of each setting: -#### `auto-dark-dark-theme` - -The theme to enable when dark-mode is active. +#### `auto-dark-themes` +A list containing two elements. The first is the list of themes to enable when +dark-mode is active and the second is the list of themes to enable when +dark-mode is inactive. -Possible values are themes installed on your system found by -`customize-themes` or `nil` to use Emacs with no themes (default -appearance). +Possible values for each sublist are themes installed on your system found by +`customize-themes` or `nil` to use Emacs with no themes (default appearance). -#### `auto-dark-light-theme` -The theme to enable when dark-mode is inactive. +If this variable is `nil`, then the set of themes from `custom-enabled-themes` +will be used for both dark and light mode. These themes must support +`frame-background-mode`, or else there will be no visible change. -Possible values are themes installed on your system found by -`customize-themes` or `nil` to use Emacs with no themes (default -appearance). +**NB**: When adding themes to this list, switching between light and dark, or +initializing Emacs, you may see a prompt like “Loading a theme can run Lisp +code. Really load?” If you answer “yes” and allow Emacs to treat the theme as +safe in future sessions, you should only see this prompt once per theme. To +disable the prompt completely, you can set `custom-safe-themes` to `t` before +setting `auto-dark-themes`. #### `auto-dark-polling-interval-seconds` @@ -261,4 +274,3 @@ This package in action: - Linux (Gnome DE) ![auto-dark-emacs in action - linux gnome](images/demo_gnome.gif) - diff --git a/auto-dark.el b/auto-dark.el index 367b530..4dda3f5 100644 --- a/auto-dark.el +++ b/auto-dark.el @@ -1,11 +1,12 @@ -;;; auto-dark.el --- Automatically sets the dark-mode theme based on macOS/Linux/Windows status -*- lexical-binding: t; -*- +;;; auto-dark.el --- Automatically set the dark-mode theme based on system status -*- lexical-binding: t; -*- ;; Author: Rahul M. Juliato ;; Tim Harper ;; Vincent Zhang ;; Jonathan Arnett +;; Greg Pfeil ;; Created: July 16 2019 -;; Version: 0.12 +;; Version: 0.13.2 ;; Keywords: macos, windows, linux, themes, tools, faces ;; URL: https://github.com/LionyxML/auto-dark-emacs ;; Package-Requires: ((emacs "24.4")) @@ -37,15 +38,19 @@ :group 'tools :prefix "auto-dark-*") -(defcustom auto-dark-dark-theme 'wombat - "The theme to enable when dark-mode is active." - :group 'auto-dark - :type '(choice symbol (const nil))) +(defun auto-dark--initialize-after-init (&optional initialize-fn) + "Return a function suitable for passing to `defcustom'’s :initialize parameter. +The returned function defers initialization to `after-init-hook'. INITIALIZE-FN +is the function that should be used to eventually initialize the symbol. It +defaults to `custom-initialize-reset', as does :initialize itself." + (let ((init-fn (or initialize-fn #'custom-initialize-reset))) + (lambda (symbol exp) + (if after-init-time + (funcall init-fn symbol exp) + (add-hook 'after-init-hook (lambda () (funcall init-fn symbol exp))))))) -(defcustom auto-dark-light-theme 'leuven - "The theme to enable when dark-mode is inactive." - :group 'auto-dark - :type '(choice symbol (const nil))) +(make-obsolete-variable 'auto-dark-dark-theme 'auto-dark-themes "0.13") +(make-obsolete-variable 'auto-dark-light-theme 'auto-dark-themes "0.13") (defcustom auto-dark-polling-interval-seconds 5 "The number of seconds between which to poll for dark mode state. @@ -80,13 +85,13 @@ doing!" (defvar auto-dark--dbus-listener-object nil) -(defun auto-dark--is-dark-mode-applescript () +(defun auto-dark--current-mode-applescript () "Invoke AppleScript using Emacs built-in AppleScript support. In order to check if dark mode is enabled. Return true if it is." (if (fboundp 'ns-do-applescript) - (auto-dark--is-dark-mode-ns) + (if (auto-dark--is-dark-mode-ns) 'dark 'light) (if (fboundp 'mac-do-applescript) - (auto-dark--is-dark-mode-mac) + (if (auto-dark--is-dark-mode-mac) 'dark 'light) (error "No AppleScript support available in this Emacs build. Try setting `auto-dark-allow-osascript` to t")))) (defconst auto-dark--is-dark-mode-applescript @@ -96,15 +101,22 @@ It is “true” if the system is in dark mode, and “false” otherwise.") (defun auto-dark--is-dark-mode-ns () "Check if dark mode is enabled using `ns-do-applescript'." - ;; `ns-do-applescript' doesn’t support booleans, so we convert to an integer. - (/= 0 - (ns-do-applescript (concat auto-dark--is-dark-mode-applescript - " as integer")))) + ;; FIXME: We shouldn’t need to check `fboundp' on every call, just when + ;; setting the detection method. + (when (fboundp 'ns-do-applescript) + ;; `ns-do-applescript' doesn’t support booleans, so we convert to an + ;; integer. + (/= 0 + (ns-do-applescript (concat auto-dark--is-dark-mode-applescript + " as integer"))))) (defun auto-dark--is-dark-mode-mac () "Check if dark mode is enabled using `mac-do-applescript'." - (string-equal "true" - (mac-do-applescript auto-dark--is-dark-mode-applescript))) + ;; FIXME: We shouldn’t need to check `fboundp' on every call, just when + ;; setting the detection method. + (when (fboundp 'mac-do-applescript) + (string-equal "true" + (mac-do-applescript auto-dark--is-dark-mode-applescript)))) (defun auto-dark--is-dark-mode-osascript () "Invoke AppleScript using Emacs using external shell command; @@ -112,19 +124,24 @@ this is less efficient, but works for non-GUI Emacs." (equal '("true") (process-lines "osascript" "-e" auto-dark--is-dark-mode-applescript))) -(defun auto-dark--is-dark-mode-dbus () +(defun auto-dark--current-mode-dbus () "Use Emacs built-in D-Bus function to determine if dark theme is enabled." - (eq 1 (caar (dbus-ignore-errors - (dbus-call-method - :session - "org.freedesktop.portal.Desktop" - "/org/freedesktop/portal/desktop" - "org.freedesktop.portal.Settings" "Read" - "org.freedesktop.appearance" "color-scheme"))))) + (pcase (caar (dbus-ignore-errors + (dbus-call-method + :session + "org.freedesktop.portal.Desktop" + "/org/freedesktop/portal/desktop" + "org.freedesktop.portal.Settings" "Read" + "org.freedesktop.appearance" "color-scheme"))) + (0 nil) + (1 'dark) + (2 'light))) (defun auto-dark--is-dark-mode-powershell () "Invoke powershell using Emacs using external shell command." - (string-equal "0" (string-trim (shell-command-to-string "powershell -noprofile -noninteractive \ + (string-equal "0" + (string-trim (shell-command-to-string + "powershell -noprofile -noninteractive \ -nologo -ex bypass -command Get-ItemPropertyValue \ HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize \ -Name AppsUseLightTheme")))) @@ -132,14 +149,21 @@ HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize \ (defun auto-dark--is-dark-mode-winreg () "Use Emacs built-in Windows Registry function. In order to determine if dark theme is enabled." - (eq 0 (w32-read-registry 'HKCU - "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize" - "AppsUseLightTheme"))) + ;; FIXME: We shouldn’t need to check `fboundp' on every call, just when + ;; setting the detection method. + (when (fboundp 'w32-read-registry) + (eq 0 + (w32-read-registry + 'HKCU + "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize" + "AppsUseLightTheme")))) (defun auto-dark--is-dark-mode-termux () - "Use Termux way to determine if dark theme is enabled. ref: https://github.com/termux/termux-api/issues/425." + "Use Termux way to determine if dark theme is enabled. +ref: https://github.com/termux/termux-api/issues/425." (string-equal "Night mode: yes" - (shell-command-to-string "echo -n $(cmd uimode night 2>&1 &1 +;; Created: October 11 2024 +;; Version: 0.1.0 +;; Keywords: maint, tools +;; URL: https://github.com/LionyxML/auto-dark-emacs +;; SPDX-License-Identifier: AGPL-3.0-or-later + +;;; Commentary: + +;; This library attempts to simulate the Emacs initialization process, for +;; writing tests that are sensitive to initialization. + +;;; Code: + +(defvar auto-dark-initialize--current-state nil + "The phase of initialization we’ve gotten to. +So users don’t have to call every function.") + +(defun auto-dark-initialize-start () + "Set up various things to a state similar to what existed before early init." + (if auto-dark-initialize--current-state + (lwarn 'auto-dark-initialize :warn + "Initialization is already at least as far as 'start, not running.") + (setq after-init-time nil + auto-dark-initialize--current-state 'start))) + +(defun auto-dark-initialize-after-early-init (autoload-features) + "Update state as done between early init and the rest of the init files. +E.g., initializing the package system and setting up the display. +AUTOLOAD-FEATURES should be a list of symbols naming the autoload features for +packages you are testing." + (unless auto-dark-initialize--current-state + (auto-dark-initialize-start)) + (if (eq auto-dark-initialize--current-state 'start) + (progn + (mapc (lambda (feature) (require feature)) autoload-features) + (custom-set-variables + '(frame-background-mode 'light)) + (setq auto-dark-initialize--current-state 'early-init)) + (lwarn 'auto-dark-initialize :warn + "Initialization is already at least as far as 'early-init, not running."))) + +(defun auto-dark-initialize-finish () + "This is the rest of the init process after the various files are loaded. +After this is run, the tests should reflect a “normal” Emacs session." + (pcase auto-dark-initialize--current-state + ('(nil start) + (signal 'auto-dark-initialize + '("At least ‘auto-dark-initialize-after-early-init needs to be called first.’"))) + ('early-init + (setq after-init-time (current-time)) + (run-hooks 'after-init-hook) + (setq auto-dark-initialize--current-state 'finished)) + (_ (lwarn 'auto-dark-initialize :warn + "Initialization has already finished, not running.")))) + +(provide 'auto-dark-initialize) +;;; auto-dark-initialize.el ends here diff --git a/nix/checks.nix b/nix/checks.nix new file mode 100644 index 0000000..5f11d05 --- /dev/null +++ b/nix/checks.nix @@ -0,0 +1,81 @@ +{ + checkedDrv, + emacsPackages, + emacsWithPackages, + src, + stdenv, +}: let + lib = import ./lib.nix {inherit emacsPackages;}; +in { + doctor = checkedDrv (stdenv.mkDerivation { + inherit src; + inherit (lib) ELDEV_LOCAL; + + name = "eldev doctor"; + + nativeBuildInputs = [ + (emacsWithPackages (e: [e.elisp-lint])) + # Emacs-lisp build tool, https://doublep.github.io/eldev/ + emacsPackages.eldev + ]; + + buildPhase = '' + runHook preBuild + ## TODO: Currently needed to make a temp file in + ## `eldev--create-internal-pseudoarchive-descriptor`. + HOME="$(mktemp --directory --tmpdir fake-home.XXXXXX)" + mkdir -p "$HOME/.cache/eldev" + ## NB: `EMACSNATIVELOADPATH` is needed by `elisp-lin + EMACSNATIVELOADPATH= eldev doctor + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p "$out" + runHook postInstall + ''; + }); + + lint = let + emacs = emacsWithPackages (e: [ + e.elisp-lint + e.package-lint + e.relint + ]); + in + checkedDrv (stdenv.mkDerivation { + inherit src; + inherit (lib) ELDEV_LOCAL; + + name = "eldev lint"; + + nativeBuildInputs = [ + emacs + emacsPackages.eldev + ]; + + postPatch = lib.setUpLocalDependencies emacs.deps; + + buildPhase = '' + runHook preBuild + + ## Need `--external` here so that we don’t try to download any + ## package archives (which would break the sandbox). + ## NB: `EMACSNATIVELOADPATH` is needed by `elisp-lint`. + ## TODO: Currently need `HOME` to make a temp file in + ## `eldev--create-internal-pseudoarchive-descriptor`. + EMACSNATIVELOADPATH= \ + HOME="$(mktemp --directory --tmpdir fake-home.XXXXXX)" \ + eldev --external lint --required + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p "$out" + runHook preInstall + ''; + }); +} diff --git a/nix/lib.nix b/nix/lib.nix new file mode 100644 index 0000000..e6e588b --- /dev/null +++ b/nix/lib.nix @@ -0,0 +1,21 @@ +{emacsPackages}: let + emacsPath = package: "${package}/share/emacs/site-lisp/elpa/${package.pname}-${package.version}"; +in { + ## We need to tell Eldev where to find its Emacs package. + ELDEV_LOCAL = emacsPath emacsPackages.eldev; + + ## Ideally this could just + ## (setq eldev-external-package-dir "${deps}/share/emacs/site-lisp/elpa") + ## but Eldev wants to write to that directory, even if there's nothing to + ## download. + setUpLocalDependencies = deps: '' + { + echo + echo "(mapcar 'eldev-use-local-dependency" + echo " (directory-files" + echo " \"${deps}/share/emacs/site-lisp/elpa\"" + echo " t" + echo " directory-files-no-dot-files-regexp))" + } >> Eldev + ''; +} diff --git a/nix/packages/auto-dark.nix b/nix/packages/auto-dark.nix new file mode 100644 index 0000000..1a20952 --- /dev/null +++ b/nix/packages/auto-dark.nix @@ -0,0 +1,81 @@ +{ + checkedDrv, + emacsPackages, + src, +}: let + lib = import ../lib.nix {inherit emacsPackages;}; + + ## Read version in format: ;; Version: x.y(.z)? + readVersion = fp: + builtins.elemAt + (builtins.match + ".*(;; Version: ([[:digit:]]+\.[[:digit:]]+(\.[[:digit:]]+)?)).*" + (builtins.readFile fp)) + 1; + + emacsWithPackages = emacsPackages.emacsWithPackages (epkgs: [ + epkgs.buttercup + epkgs.elisp-lint + epkgs.use-package # TODO: Conditionalize on Emacs <29? + ]); +in + checkedDrv (emacsPackages.trivialBuild { + inherit src; + inherit (lib) ELDEV_LOCAL; + + pname = "auto-dark"; + + version = readVersion ../../auto-dark.el; + + nativeBuildInputs = [ + emacsWithPackages + # Emacs-lisp build tool, https://doublep.github.io/eldev/ + emacsPackages.eldev + ]; + + postPatch = lib.setUpLocalDependencies emacsWithPackages.deps; + + doCheck = true; + + checkPhase = '' + runHook preCheck + + ## TODO: Currently needed to make a temp file in + ## `eldev--create-internal-pseudoarchive-descriptor`. + HOME="$(mktemp --directory --tmpdir fake-home.XXXXXX)" + + ## Need `--external` here so that we don’t try to download any + ## package archives (which would break the sandbox). + eldev --external test --test-type=main + + runHook postCheck + ''; + + doInstallCheck = true; + + installCheckPhase = '' + runHook preInstallCheck + + init_tests=($(IFS=$'\n' find ./tests/initialization -type f -name '*.el')) + # This tries to make sure we don’t have a bug that skips the tests. + if (( ''${#init_tests[@]} == 0 )); then + echo "didn’t find initialization tests" >2 + exit 1 + else + for file in "''${init_tests[@]}"; do + echo "testing $file" + eldev --external test --test-type=integration -f "$file" + done + fi + + ## TODO: Currently needed to make a temp file in + ## `eldev--create-internal-pseudoarchive-descriptor`. + HOME="$(mktemp --directory --tmpdir fake-home.XXXXXX)" + + ## Need `--external` here so that we don’t try to download any + ## package archives (which would break the sandbox). + eldev --external --packaged test --test-type=main + + runHook postInstallCheck + ''; + }) diff --git a/tests/basic.el b/tests/basic.el new file mode 100644 index 0000000..57df10f --- /dev/null +++ b/tests/basic.el @@ -0,0 +1,62 @@ +;;; basic.el --- Tests that work in any environment -*- lexical-binding: t; -*- + +;;; Commentary: + +;;; Code: + +(require 'auto-dark) +(require 'buttercup) + +;; Track whether `auto-dark-mode' was enabled before we started the tests. +(defvar auto-dark-tests--original-state auto-dark-mode) +(defvar auto-dark-tests--original-enabled-themes custom-enabled-themes) + +(custom-set-variables + ;; TODO: This is not a valid value, but it stops Auto-Dark from trying to set + ;; the detection method. See #73. + '(auto-dark-detection-method 'manual) + '(custom-enabled-themes ()) + '(frame-background-mode 'light)) + +;; Ensure `auto-dark-mode' is disabled for this part of the test. +(when auto-dark-tests--original-state + (auto-dark-mode -1)) + +(describe "when disabled" + (it "shouldn’t affect enabled themes" + (expect custom-enabled-themes :to-be nil) + (custom-set-variables + '(auto-dark-themes '((tango-dark) (tango)))) + (expect auto-dark-themes :to-equal '((tango-dark) (tango))) + (expect custom-enabled-themes :to-be nil))) + +(describe "when enabled" + (it "should immediately update themes" + (expect auto-dark-themes :to-equal '((tango-dark) (tango))) + (auto-dark-mode 1) + (expect auto-dark-themes :to-equal '((tango-dark) (tango))) + (expect custom-enabled-themes :to-equal '(tango))) + (it "should reflect changes to themes immediately" + (custom-set-variables + '(auto-dark-themes '((wombat) (leuven)))) + (expect auto-dark-themes :to-equal '((wombat) (leuven))) + (expect custom-enabled-themes :to-equal '(leuven)))) + +(describe "toggling" + (before-each + (auto-dark-toggle-appearance)) + (it "should invert the current appearance" + (expect custom-enabled-themes :to-equal '(wombat))) + (it "should invert the current appearance again" + (expect custom-enabled-themes :to-equal '(leuven))) + (it "should invert the current appearance and again" + (expect custom-enabled-themes :to-equal '(wombat)))) + +;; Restore the original state +(unless auto-dark-tests--original-state + (auto-dark-mode -1)) + +(custom-set-variables + '(custom-enabled-themes auto-dark-tests--original-enabled-themes)) + +;;; basic.el ends here diff --git a/tests/initialization/README.md b/tests/initialization/README.md new file mode 100644 index 0000000..ae25789 --- /dev/null +++ b/tests/initialization/README.md @@ -0,0 +1,5 @@ +# initialization tests + +These files test that the package behaves in various contexts when first loaded. + +They each need to be run in their own instance, to ensure they don’t interfere with each other. diff --git a/tests/initialization/customize-first.el b/tests/initialization/customize-first.el new file mode 100644 index 0000000..c344083 --- /dev/null +++ b/tests/initialization/customize-first.el @@ -0,0 +1,60 @@ +;;; customize-first.el --- Customize vars before enabling -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Tests the recommended flow of customizing variables before enabling +;; Auto-Dark. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +(describe "package initialization with variables pre-set" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads)) + (custom-set-variables + '(auto-dark-themes '((tsdh-dark) (tsdh-light))))) + (describe "before enabling" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "after enabling" + (before-all + (auto-dark-mode 1)) + (it "themes should be enabled" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))) + (it "should have bound Auto-Dark variables" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil))) + + (describe "after init" + (before-all + (auto-dark-initialize-finish)) + (it "themes should be enabled" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))) + (it "Old Auto-Dark variables should now be set" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + (expect auto-dark-dark-theme :to-be 'wombat) + (expect auto-dark-light-theme :to-be 'leuven)))) + +;;; customize-first.el ends here diff --git a/tests/initialization/de-facto-old.el b/tests/initialization/de-facto-old.el new file mode 100644 index 0000000..4f0c7f1 --- /dev/null +++ b/tests/initialization/de-facto-old.el @@ -0,0 +1,84 @@ +;;; de-facto-old.el --- Tests that need to be run in a fresh instance -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This models the most common initialization style of Emacs, where Customize +;; inserts a `custom-set-variables' form at the end of `user-init-file'. + +;; 1. `package-initialize' is called, then +;; 2. `user-init-file' is loaded, which has +;; a. some initialization of `auto-dark-mode' and +;; b. `custom-set-variables' at the end. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +;; To silence “reference to free variable” warnings +(defvar auto-dark-allow-osascript) +(defvar auto-dark-allow-powershell) +(defvar auto-dark-dark-theme) +(defvar auto-dark-light-theme) +(defvar auto-dark-polling-interval-seconds) +(defvar auto-dark-themes) + +(describe "Emacs’s de-facto initialization with old theme vars" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads))) + + (describe "after ‘package-initialize’ is called" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "before ‘custom-set-variables’ is called" + (before-all + (auto-dark-mode 1)) + + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-equal ())) + (it "should have bound Auto-Dark variables to their standard values" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-be nil) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil))) + + (describe "after vars are set" + (before-all + (custom-set-variables + '(auto-dark-dark-theme 'tsdh-dark) + '(auto-dark-light-theme 'tsdh-light))) + + ;; NB: When using the old variables, they won’t be set until + ;; `after-init-hook' is run. + (it "shouldn’t have changed anything yet" + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil))) + + (describe "after ‘user-init-file’ is loaded" + (before-all + (auto-dark-initialize-finish)) + + (it "should have configured Auto-Dark" + (expect auto-dark-themes :to-be nil) + ;; And _now_ these have their standard values. + (expect auto-dark-dark-theme :to-be 'tsdh-dark) + (expect auto-dark-light-theme :to-be 'tsdh-light)) + + (it "should have enabled the correct themes" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))))) + +;;; de-facto-old.el ends here diff --git a/tests/initialization/de-facto.el b/tests/initialization/de-facto.el new file mode 100644 index 0000000..b4f7078 --- /dev/null +++ b/tests/initialization/de-facto.el @@ -0,0 +1,74 @@ +;;; de-facto.el --- Tests that need to be run in a fresh instance -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This models the most common initialization style of Emacs, where Customize +;; inserts a `custom-set-variables' form at the end of `user-init-file'. + +;; 1. `package-initialize' is called, then +;; 2. `user-init-file' is loaded, which has +;; a. some initialization of `auto-dark-mode' and +;; b. `custom-set-variables' at the end. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +;; To silence “reference to free variable” warnings +(defvar auto-dark-allow-osascript) +(defvar auto-dark-allow-powershell) +(defvar auto-dark-dark-theme) +(defvar auto-dark-detection-method) +(defvar auto-dark-light-theme) +(defvar auto-dark-polling-interval-seconds) +(defvar auto-dark-themes) + +(describe "Emacs’s de-facto initialization" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads))) + + (describe "after ‘package-initialize’ is called" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "before ‘custom-set-variables’ is called" + (before-all + (auto-dark-mode 1)) + + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-equal ())) + (it "should have bound Auto-Dark variables to their standard values" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-be nil) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil))) + + (describe "after variables are set" + (before-all + (custom-set-variables + '(auto-dark-themes '((tsdh-dark) (tsdh-light))))) + + (it "should have configured Auto-Dark" + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + ;; And these are still unbound. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil)) + + (it "should have enabled the correct themes" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))))) + +;;; de-facto.el ends here diff --git a/tests/initialization/defaults.el b/tests/initialization/defaults.el new file mode 100644 index 0000000..35d0c28 --- /dev/null +++ b/tests/initialization/defaults.el @@ -0,0 +1,73 @@ +;;; defaults.el --- Tests that need to be run in a fresh instance -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This models the most common initialization style of Emacs, where Customize +;; inserts a `custom-set-variables' form at the end of `user-init-file'. + +;; 1. `package-initialize' is called, then +;; 2. `user-init-file' is loaded, which has +;; a. some initialization of `auto-dark-mode' and +;; b. `custom-set-variables' at the end. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +;; To silence “reference to free variable” warnings +(defvar auto-dark-allow-osascript) +(defvar auto-dark-allow-powershell) +(defvar auto-dark-dark-theme) +(defvar auto-dark-detection-method) +(defvar auto-dark-light-theme) +(defvar auto-dark-polling-interval-seconds) +(defvar auto-dark-themes) + +(describe "Emacs’s de-facto initialization with old theme vars" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads))) + + (describe "after ‘package-initialize’ is called" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "before ‘custom-set-variables’ is called" + (before-all + (auto-dark-mode 1)) + + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-equal ())) + (it "should have bound Auto-Dark variables to their standard values" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-be nil) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil))) + + (describe "after ‘user-init-file’ is loaded" + (before-all + (auto-dark-initialize-finish)) + + (it "should have configured Auto-Dark" + (expect auto-dark-themes :to-be nil) + ;; And _now_ these have their standard values. + (expect auto-dark-dark-theme :to-be 'wombat) + (expect auto-dark-light-theme :to-be 'leuven)) + + (it "should have enabled the correct themes" + (expect custom-enabled-themes :to-be-in '((wombat) (leuven)))))) + +;;; defaults.el ends here diff --git a/tests/initialization/early-init.el b/tests/initialization/early-init.el new file mode 100644 index 0000000..b8ca914 --- /dev/null +++ b/tests/initialization/early-init.el @@ -0,0 +1,82 @@ +;;; early-init.el --- Customize in early-init -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This is more of an edge case, but it customizes the variables in early init, +;; and then enables the package in regular init. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +;; To silence “reference to free variable” warnings +(defvar auto-dark-themes) + +(describe "customization in early init" + (before-all + (auto-dark-initialize-start) + (custom-set-variables + '(auto-dark-themes '((tsdh-dark) (tsdh-light))))) + + (describe "in early init" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ()) + (expect (boundp 'auto-dark-themes) :to-be nil)) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "after early init" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads))) + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "after package is loaded" + (before-all + (require 'auto-dark)) + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should have bound Auto-Dark variables" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil))) + + ;; FIXME: Make sure this is always in a particular mode, or check for either set. + (describe "after-enabling" + (before-all + (auto-dark-mode 1)) + (it "themes should be enabled" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))) + (it "Auto-Dark variables should be unchanged" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil)))) + +;;; early-init.el ends here diff --git a/tests/initialization/use-package-config.el b/tests/initialization/use-package-config.el new file mode 100644 index 0000000..cad992b --- /dev/null +++ b/tests/initialization/use-package-config.el @@ -0,0 +1,62 @@ +;;; use-package-config.el --- Use-package with :config -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This tests a common `use-package' style of initialization. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +(describe "‘use-package’ initialization with :config" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads))) + + (describe "after ‘package-initialize’ is called" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "after ‘user-init-file’ is loaded" + (before-all + (use-package auto-dark + :custom (auto-dark-themes '((tsdh-dark) (tsdh-light))) + :config (auto-dark-mode))) + + (it "should have configured Auto-Dark" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil)) + (it "should have enabled the themes" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light))))) + + (describe "after init" + (before-all + (auto-dark-initialize-finish)) + + (it "should have configured Auto-Dark" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + (expect auto-dark-dark-theme :to-be 'wombat) + (expect auto-dark-light-theme :to-be 'leuven)) + (it "should have enabled the themes" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))))) + +;;; use-package-config.el ends here diff --git a/tests/initialization/use-package-init.el b/tests/initialization/use-package-init.el new file mode 100644 index 0000000..1197de8 --- /dev/null +++ b/tests/initialization/use-package-init.el @@ -0,0 +1,55 @@ +;;; use-package-init.el --- Use-package with :init -*- lexical-binding: t; -*- + +;;; Commentary: + +;; This tests a common `use-package' style of initialization. + +;;; Code: + +(require 'auto-dark-initialize) +(require 'buttercup) + +;; To silence “reference to free variable” warnings +(defvar auto-dark-allow-osascript) +(defvar auto-dark-allow-powershell) +(defvar auto-dark-dark-theme) +(defvar auto-dark-light-theme) +(defvar auto-dark-polling-interval-seconds) +(defvar auto-dark-themes) + +(describe "‘use-package’ initialization with :init" + (before-all + (auto-dark-initialize-after-early-init '(auto-dark-autoloads))) + + (describe "after ‘package-initialize’ is called" + (it "should not have enabled the themes yet" + (expect custom-enabled-themes :to-be ())) + (it "should not have bound Auto-Dark variables" + (expect (boundp 'auto-dark-allow-osascript) :to-be nil) + (expect (boundp 'auto-dark-allow-powershell) :to-be nil) + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil) + (expect (boundp 'auto-dark-polling-interval-seconds) :to-be nil) + (expect (boundp 'auto-dark-themes) :to-be nil))) + + (describe "after ‘user-init-file’ is loaded" + (before-all + (use-package auto-dark + :custom (auto-dark-themes '((tsdh-dark) (tsdh-light))) + :config (auto-dark-mode))) + + (it "should have configured Auto-Dark" + (expect auto-dark-allow-osascript :to-be nil) + (expect auto-dark-allow-powershell :to-be nil) + (expect (boundp 'auto-dark-detection-method) :to-be-truthy) + (expect auto-dark-polling-interval-seconds :to-be 5) + (expect auto-dark-themes :to-equal '((tsdh-dark) (tsdh-light))) + ;; These two are handled specially – they aren’t set to their defaults + ;; until after initialization. + (expect (boundp 'auto-dark-dark-theme) :to-be nil) + (expect (boundp 'auto-dark-light-theme) :to-be nil)) + (it "should have enabled the themes" + (expect custom-enabled-themes :to-be-in '((tsdh-dark) (tsdh-light)))))) + +;;; use-package-init.el ends here