summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.localrepo/archive-contents8
-rw-r--r--.localrepo/dired-sidebar-20250212.629.tarbin51200 -> 0 bytes
-rw-r--r--.localrepo/erc-image-20210604.753.tarbin0 -> 20480 bytes
-rw-r--r--.localrepo/erc-yank-20210220.1815.tarbin0 -> 10240 bytes
-rw-r--r--.localrepo/nerd-icons-dired-20250506.1729.tarbin10240 -> 0 bytes
-rw-r--r--.localrepo/org-msg-4.0.tarbin0 -> 10240 bytes
-rw-r--r--.localrepo/org-web-tools-20231220.1515.tarbin0 -> 51200 bytes
-rw-r--r--.localrepo/plz-0.9.1.tarbin0 -> 204800 bytes
-rw-r--r--.localrepo/wttrin-0.2.3.tarbin0 -> 10240 bytes
-rw-r--r--ai-prompts/quality-engineer.org367
-rw-r--r--init.el11
-rw-r--r--modules/custom-buffer-file.el (renamed from modules/custom-file-buffer.el)6
-rw-r--r--modules/mail-config.el4
-rw-r--r--modules/org-agenda-config.el20
-rw-r--r--modules/org-gcal-config.el13
-rw-r--r--modules/org-roam-config.el1
-rw-r--r--modules/popper-config.el1
-rw-r--r--modules/prog-general.el8
-rw-r--r--modules/selection-framework.el25
-rw-r--r--modules/system-commands.el138
-rw-r--r--modules/weather-config.el3
-rw-r--r--modules/wip.el154
-rw-r--r--tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el (renamed from tests/test-custom-file-buffer-clear-to-bottom-of-buffer.el)10
-rw-r--r--tests/test-custom-buffer-file-clear-to-top-of-buffer.el (renamed from tests/test-custom-file-buffer-clear-to-top-of-buffer.el)10
-rw-r--r--tests/test-custom-buffer-file-copy-link-to-buffer-file.el (renamed from tests/test-custom-file-buffer-copy-link-to-buffer-file.el)10
-rw-r--r--tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el (renamed from tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el)10
-rw-r--r--tests/test-custom-buffer-file-copy-whole-buffer.el (renamed from tests/test-custom-file-buffer-copy-whole-buffer.el)10
-rw-r--r--tests/test-custom-buffer-file-delete-buffer-and-file.el (renamed from tests/test-custom-file-buffer-delete-buffer-and-file.el)10
-rw-r--r--tests/test-custom-buffer-file-move-buffer-and-file.el (renamed from tests/test-custom-file-buffer-move-buffer-and-file.el)10
-rw-r--r--tests/test-custom-buffer-file-rename-buffer-and-file.el (renamed from tests/test-custom-file-buffer-rename-buffer-and-file.el)10
-rw-r--r--tests/test-org-gcal-mock.el112
31 files changed, 706 insertions, 245 deletions
diff --git a/.localrepo/archive-contents b/.localrepo/archive-contents
index d6c4f6b4..c823da50 100644
--- a/.localrepo/archive-contents
+++ b/.localrepo/archive-contents
@@ -39,7 +39,6 @@
(devdocs . [(0 6 1) ((emacs (27 1))) "Emacs viewer for DevDocs" tar])
(dired-hacks-utils . [(20240629 1906) ((dash (2 5 0)) (emacs (24 3))) "Utilities and helpers for dired-hacks collection." tar])
(dired-hide-dotfiles . [(20240727 1720) ((emacs (25 1))) "Hide dotfiles in dired." tar])
- (dired-sidebar . [(20250212 629) ((emacs (25 1)) (dired-subtree (0 0 1)) (compat (30 0 0 0))) "Tree browser leveraging dired." tar])
(dired-subtree . [(20240629 1859) ((dash (2 5 0)) (dired-hacks-utils (0 0 1)) (emacs (24 3))) "Insert subdirectories in a tree-like fashion." tar])
(dirvish . [(2 3 0) ((emacs (28 1)) (compat (30))) "A modern file manager based on dired mode" tar])
(doom-modeline . [(20250718 1833) ((emacs (25 1)) (compat (30 1 0 0)) (nerd-icons (0 1 0)) (shrink-path (0 3 1))) "A minimal and modern mode-line." tar])
@@ -57,6 +56,8 @@
(emms . [(24) ((cl-lib (0 5)) (nadvice (0 3)) (seq (0))) "The Emacs Multimedia System" tar])
(emojify . [(20210108 1111) ((seq (1 11)) (ht (2 0)) (emacs (24 3))) "Display emojis in Emacs." tar])
(eradio . [(20210327 1000) ((emacs (24 1))) "A simple Internet radio player." tar])
+ (erc-image . [(20210604 753) nil "Show received image urls in the ERC buffer." tar])
+ (erc-yank . [(20210220 1815) nil "Automagically create a Gist if pasting more than 5 lines." tar])
(eshell-syntax-highlighting . [(20241222 2030) ((emacs (25 1))) "Highlight eshell commands." tar])
(eshell-toggle . [(20250513 1742) ((emacs (25 1)) (dash (2 11 0))) "Show/hide eshell under active window." tar])
(eshell-up . [(20240226 1747) ((emacs (24))) "Quickly go to a specific parent directory in eshell." tar])
@@ -112,7 +113,6 @@
(move-text . [(20231204 1514) nil "Move current line or region with Mdown." tar])
(nerd-icons . [(20250718 355) ((emacs (24 3))) "Emacs Nerd Font Icons Library." tar])
(nerd-icons-completion . [(20250509 1949) ((emacs (25 1)) (nerd-icons (0 0 1)) (compat (30))) "Add icons to completion candidates." tar])
- (nerd-icons-dired . [(20250506 1729) ((emacs (24 4)) (nerd-icons (0 0 1))) "Shows icons for each file in dired mode." tar])
(nerd-icons-ibuffer . [(20250307 958) ((emacs (24 3)) (nerd-icons (0 0 1))) "Display nerd icons in ibuffer." tar])
(noflet . [(20141102 1454) nil "Locally override functions." tar])
(nov . [(20250615 1051) ((esxml (0 3 6)) (emacs (25 1))) "Featureful EPUB reader mode." tar])
@@ -124,8 +124,10 @@
(org-contacts . [(1 1) ((emacs (27 1)) (org (9 3 4))) "Contacts management system for Org Mode" tar])
(org-drill . [(2 7 0) ((emacs (25 3)) (seq (2 14)) (org (9 3)) (persist (0 3))) "Self-testing using spaced repetition" tar])
(org-gcal . [(20250624 1628) ((aio (1 0)) (alert (1 2)) (elnode (20190702 1509)) (emacs (26 1)) (oauth2-auto (20240326 2225)) (org (9 3)) (persist (0 4)) (request (20190901)) (request-deferred (20181129))) "Org sync with Google Calendar." tar])
+ (org-msg . [(4 0) ((emacs (24 4)) (htmlize (1 54))) "No description available." tar])
(org-roam . [(20250701 528) ((emacs (26 1)) (dash (2 13)) (org (9 6)) (emacsql (4 1 0)) (magit-section (3 0 0))) "A database abstraction layer for Org-mode." tar])
(org-superstar . [(1 5 1) ((org (9 1 9)) (emacs (26 1))) "Prettify headings and plain lists in Org mode" tar])
+ (org-web-tools . [(20231220 1515) ((emacs (27 1)) (org (9 0)) (compat (29 1 4 2)) (dash (2 12)) (esxml (0 3 4)) (s (1 10 0)) (plz (0 7 1)) (request (0 3 0))) "Display and capture web content with Org-mode." tar])
(ox-pandoc . [(20250424 908) ((org (8 2)) (emacs (24 4)) (dash (2 8)) (ht (2 0))) "An Org-mode exporter using pandoc." tar])
(package-build . [(20250708 1908) ((emacs (26 1)) (compat (30 0 0 0))) "Tools for assembling a package archive." tar])
(package-lint . [(0 26) ((emacs (24 4)) (let-alist (1 0 6))) "A linting library for elisp package authors" tar])
@@ -133,6 +135,7 @@
(pdf-tools . [(1 1 0) ((emacs (26 3)) (tablist (1 0)) (let-alist (1 0 4))) "Support library for PDF documents" tar])
(pdf-view-restore . [(20190904 1708) ((pdf-tools (0 90)) (emacs (26 0))) "Support for opening last known pdf position in pdfview mode." tar])
(persist . [(0 6 1) ((emacs (26 1))) "Persist Variables between Emacs Sessions" tar])
+ (plz . [(0 9 1) ((emacs (27 1))) "HTTP library" tar])
(poetry . [(20240329 1103) ((transient (0 2 0)) (pyvenv (1 2)) (emacs (25 1))) "Interface to Poetry." tar])
(popper . [(0 4 8) ((emacs (26 1))) "Summon and dismiss buffers as popups" tar])
(popup . [(0 5 9) ((emacs (24 3))) "Visual Popup User Interface" tar])
@@ -176,6 +179,7 @@
(windsize . [(20181029 2257) nil "Simple, intuitive window resizing." tar])
(with-editor . [(3 4 4) ((emacs (26 1)) (compat (30 1))) "Use the Emacsclient as $EDITOR" tar])
(ws-butler . [(1 3) ((emacs (24 1))) "Unobtrusively remove trailing whitespace" tar])
+ (wttrin . [(0 2 3) ((emacs (24 4)) (xterm-color (1 0))) "No description available." tar])
(xterm-color . [(20230321 3) ((emacs (24 4))) "ANSI, XTERM 256 and Truecolor support." tar])
(yaml . [(1 2 0) ((emacs (25 1))) "YAML parser for Elisp" tar])
(yaml-mode . [(0 0 16) ((emacs (24 1))) "Major mode for editing YAML files" tar])
diff --git a/.localrepo/dired-sidebar-20250212.629.tar b/.localrepo/dired-sidebar-20250212.629.tar
deleted file mode 100644
index e6920db9..00000000
--- a/.localrepo/dired-sidebar-20250212.629.tar
+++ /dev/null
Binary files differ
diff --git a/.localrepo/erc-image-20210604.753.tar b/.localrepo/erc-image-20210604.753.tar
new file mode 100644
index 00000000..66c998ac
--- /dev/null
+++ b/.localrepo/erc-image-20210604.753.tar
Binary files differ
diff --git a/.localrepo/erc-yank-20210220.1815.tar b/.localrepo/erc-yank-20210220.1815.tar
new file mode 100644
index 00000000..74c1b960
--- /dev/null
+++ b/.localrepo/erc-yank-20210220.1815.tar
Binary files differ
diff --git a/.localrepo/nerd-icons-dired-20250506.1729.tar b/.localrepo/nerd-icons-dired-20250506.1729.tar
deleted file mode 100644
index 5c6d9b5c..00000000
--- a/.localrepo/nerd-icons-dired-20250506.1729.tar
+++ /dev/null
Binary files differ
diff --git a/.localrepo/org-msg-4.0.tar b/.localrepo/org-msg-4.0.tar
new file mode 100644
index 00000000..9df64990
--- /dev/null
+++ b/.localrepo/org-msg-4.0.tar
Binary files differ
diff --git a/.localrepo/org-web-tools-20231220.1515.tar b/.localrepo/org-web-tools-20231220.1515.tar
new file mode 100644
index 00000000..c7136aa3
--- /dev/null
+++ b/.localrepo/org-web-tools-20231220.1515.tar
Binary files differ
diff --git a/.localrepo/plz-0.9.1.tar b/.localrepo/plz-0.9.1.tar
new file mode 100644
index 00000000..2ff4507b
--- /dev/null
+++ b/.localrepo/plz-0.9.1.tar
Binary files differ
diff --git a/.localrepo/wttrin-0.2.3.tar b/.localrepo/wttrin-0.2.3.tar
new file mode 100644
index 00000000..9df64990
--- /dev/null
+++ b/.localrepo/wttrin-0.2.3.tar
Binary files differ
diff --git a/ai-prompts/quality-engineer.org b/ai-prompts/quality-engineer.org
index 253afc93..d6bb7ecb 100644
--- a/ai-prompts/quality-engineer.org
+++ b/ai-prompts/quality-engineer.org
@@ -17,11 +17,26 @@ You are an expert software quality engineer specializing in Emacs Lisp testing a
- Example: test-org-gcal--safe-substring.el
- Tests a single function in isolation with no external dependencies
- Focus: All normal, boundary, and error cases for ONE method
-- **Integration Tests**: One file per functional area
- - Naming: test-integration-<area-name>.el (naming scheme TBD)
- - Example: org-gcal-test.el (will be renamed later)
+- **Integration Tests**: One file per functional area or workflow
+ - Naming: test-integration-<area-or-workflow>.el
+ - Examples:
+ - test-integration-recurring-events.el (recurring event workflow)
+ - test-integration-complex-event-formatting.el (multiple formatting functions together)
+ - test-integration-empty-missing-data.el (edge case handling across functions)
+ - test-integration-multi-event-sync.el (multiple events interacting)
+ - test-integration-sync-workflow.el (full fetch → update → push cycle)
- Tests multiple components working together
- - May involve file I/O, multiple functions, org-mode buffers, etc.
+ - May involve file I/O, multiple functions, org-mode buffers, API interactions, etc.
+ - Focus on workflows, component interactions, and end-to-end scenarios
+ - Good integration test areas:
+ - Complete user workflows (sync, create, update, delete)
+ - Complex features involving multiple functions (recurring events, timezone handling)
+ - Cross-component interactions (org-mode ↔ API ↔ file system)
+ - Edge cases that span multiple functions (empty data, conflicts, errors)
+ - Anti-patterns to avoid:
+ - test-integration-<single-function>.el (too narrow, that's a unit test)
+ - test-integration-stuff.el (too vague, not descriptive)
+ - test-integration-1.el (numbered tests are not discoverable)
- Test utilities are in testutil-<category>.el files
- Analyze and leverage existing test utilities as appropriate
@@ -98,13 +113,160 @@ For each test case, provide:
- Handle missing dependencies by mocking them before loading the module
*** Test Naming
-- Use descriptive names: test-<module>-<function>-<scenario>-<expected-result>
+
+**** Unit Test Naming
+- Pattern: test-<module>-<function>-<category>-<scenario>-<expected-result>
- Examples:
- - test-buffer-kill-undead-buffer-should-bury
- test-org-gcal--safe-substring-normal-full-string-returns-string
- test-org-gcal--alldayp-boundary-leap-year-returns-true
+ - test-org-gcal--format-iso2org-error-nil-input-returns-nil
+- Category: normal, boundary, or error
- Make the test name self-documenting
-- Expected result should clarify what the test verifies (returns-true, returns-string, throws-error, etc.)
+- Expected result clarifies what the test verifies (returns-true, returns-string, throws-error, etc.)
+- Focus: Single function behavior in isolation
+
+**** Integration Test Naming
+- Pattern: test-integration-<area>-<scenario>-<expected-outcome>
+- Examples:
+ - test-integration-recurring-events-preserves-old-timestamps
+ - test-integration-multi-event-updates-dont-affect-others
+ - test-integration-sync-workflow-fetch-creates-new-entries
+ - test-integration-complex-formatting-description-escapes-asterisks
+ - test-integration-empty-missing-minimal-event-succeeds
+- Area: Repeat the integration area from filename for clarity
+- Scenario: What situation/workflow is being tested
+- Outcome: What should happen across the integrated components
+- Focus: Multiple components working together, not single function
+- Make the name readable as a sentence describing the integration behavior
+
+**** Integration Test Docstrings
+Integration tests should have more detailed docstrings than unit tests:
+
+Example structure:
+#+begin_src elisp
+(ert-deftest test-integration-recurring-events-preserves-old-timestamps ()
+ "Test that recurring events preserve original timestamps across updates.
+
+When a recurring event is updated with a new instance date from Google Calendar,
+the timestamp in the org entry should remain the original series start date, not
+jump to the current instance date.
+
+Components integrated:
+- org-gcal--format-event-timestamp (timestamp formatting with recurrence)
+- org-gcal--determine-headline (headline selection)
+- org-gcal--format-description-for-drawer (description escaping)
+- org-gcal--update-entry (entry update orchestration)
+- org-element-at-point (org-mode property extraction)
+
+Validates:
+- Recurrence parameter triggers old timestamp preservation
+- Old-start/old-end passed through update workflow correctly
+- Full workflow: JSON event → parsed data → formatted timestamp → org entry"
+ ...)
+#+end_src
+
+Docstring requirements:
+1. **First line**: Brief summary (< 80 chars) - what is being tested
+2. **Context paragraph**: Why this matters, user scenario, or problem being solved
+3. **Components integrated**: Explicit list of functions/modules working together
+ - List each component with brief description of its role
+ - Include external dependencies (org-mode functions, file I/O, etc.)
+ - Show the integration boundary (what's real vs mocked)
+4. **Validates section**: What specific integration behavior is verified
+ - Data flow between components
+ - State changes across function calls
+ - Error propagation through the system
+5. **Optional sections**:
+ - Edge cases being tested
+ - Known limitations
+ - Related integration tests
+ - Performance considerations
+
+Why detailed docstrings matter for integration tests:
+- Integration failures are harder to debug than unit test failures
+- Need to understand which component interaction broke
+- Documents the integration contract between components
+- Helps maintainers understand system architecture
+- Makes test intent clear when test name is necessarily brief
+
+**CRITICAL**: Always list integrated components in docstrings:
+- Explicitly enumerate every function/module being tested together
+- Include external dependencies (org-mode, file I/O, parsers)
+- Distinguish between what's real and what's mocked
+- Show the data flow path through components
+- Name the integration boundary points
+
+Bad docstring (insufficient detail):
+#+begin_src elisp
+(ert-deftest test-integration-sync-workflow-updates-entries ()
+ "Test that sync updates org entries."
+ ...)
+#+end_src
+
+Good docstring (lists all components):
+#+begin_src elisp
+(ert-deftest test-integration-sync-workflow-updates-entries ()
+ "Test that calendar sync workflow updates org entries correctly.
+
+When user runs org-gcal-sync, events from Google Calendar should be
+fetched and org entries updated with new data while preserving local edits.
+
+Components integrated:
+- org-gcal-sync (main entry point)
+- org-gcal--get-calendar-events (API fetching)
+- org-gcal--json-read (JSON parsing)
+- org-gcal--update-entry (entry modification)
+- org-gcal--format-event-timestamp (timestamp formatting)
+- org-element-at-point (org-mode property reading)
+- write-file (persisting changes)
+
+Validates:
+- API response flows correctly through parsing → formatting → updating
+- Entry properties are updated while preserving manual edits
+- File is saved with correct content and encoding
+- Error in one event doesn't break processing of others"
+ ...)
+#+end_src
+
+Component listing best practices:
+1. **Order by call flow**: List components in the order they're called
+2. **Group by layer**: API → parsing → business logic → persistence
+3. **Include return path**: Don't forget callbacks or response handlers
+4. **Note side effects**: File writes, cache updates, state changes
+5. **Mark test doubles**: Indicate which components are mocked/stubbed
+6. **Show boundaries**: Where does your code end and framework begins?
+
+Examples of component descriptions:
+- ~org-gcal--update-entry (entry orchestration)~ - what it does in this test
+- ~org-element-at-point (REAL org-mode function)~ - not mocked
+- ~request-deferred (MOCKED, returns test data)~ - test double
+- ~file-exists-p → find-file → save-buffer (file I/O chain)~ - flow path
+- ~org-gcal--format-iso2org (date conversion, TESTED via integration)~ - tested indirectly
+
+**** Naming Comparison
+Unit tests are narrow and specific:
+- test-org-gcal--format-iso2org-error-nil-input-returns-nil
+ - Tests ONE function with ONE input scenario
+ - Very granular: specific input → specific output
+
+Integration tests are broader and scenario-focused:
+- test-integration-recurring-events-preserves-old-timestamps
+ - Tests MULTIPLE functions working together
+ - Workflow-oriented: describes behavior across components
+
+**** Naming Checklist
+For integration test files:
+- [ ] Does the name describe a coherent area/workflow?
+- [ ] Is it discoverable with glob test-integration-*.el?
+- [ ] Could someone guess what's being tested from the name?
+- [ ] Is it distinct from other integration test files?
+
+For integration test methods:
+- [ ] Does it start with test-integration-?
+- [ ] Does it include the area from the filename?
+- [ ] Can you read it as a sentence?
+- [ ] Does it describe both scenario AND expected outcome?
+- [ ] Is it specific enough to understand what failed if it breaks?
*** Code Coverage
- Aim for high coverage of critical paths (80%+ for core functionality)
@@ -273,6 +435,197 @@ Example timeline:
- Generate appropriate integration test cases for the specific implementation
- Consider testing interactions between modules
+**** When to Write Integration Tests
+Write integration tests when:
+- Multiple components must work together (API + parser + file I/O)
+- Testing complete user workflows (fetch → update → display → save)
+- Complex features span multiple functions (recurring events, timezone handling)
+- State management across function calls matters
+- Real-world scenarios combine multiple edge cases
+- Component boundaries and contracts need validation
+
+Don't write integration tests when:
+- Single function behavior can be fully tested in isolation
+- No meaningful interaction between components
+- Mocking would remove all real integration logic
+- Unit tests already cover the integration paths adequately
+
+**** What Integration Tests Should Cover
+Focus on:
+- **Complete workflows**: Full user scenarios from start to finish
+- **Component interactions**: How functions call each other and pass data
+- **State management**: Data persistence, caching, updates across calls
+- **Real dependencies**: Actual file I/O, org-mode buffers, data structures
+- **Edge case combinations**: Multiple edge cases interacting together
+- **Error propagation**: How errors flow through the system
+- **Data integrity**: Events don't interfere, state remains consistent
+
+Avoid:
+- Re-testing individual function logic (that's unit tests)
+- Testing framework/library behavior (trust it works)
+- Over-mocking that removes actual integration
+
+**** Integration Test Characteristics
+- **Slower** than unit tests (acceptable tradeoff)
+- **More setup** required (buffers, files, mock data)
+- **Broader scope** than unit tests (multiple functions)
+- **Higher value** for catching real-world bugs
+- **Less granular** in pinpointing exact failures
+- **More realistic** scenarios and data
+
+**** Integration Test Organization
+Structure integration tests by:
+1. **Workflow**: test-integration-sync-workflow.el (complete sync cycle)
+2. **Feature**: test-integration-recurring-events.el (recurring event handling)
+3. **Component interaction**: test-integration-multi-event-sync.el (multiple events)
+4. **Edge case category**: test-integration-empty-missing-data.el (nil/empty across system)
+
+Each test file should:
+- Focus on one coherent integration area
+- Include setup helpers specific to that area
+- Test realistic scenarios, not artificial combinations
+- Have clear test names describing the integration behavior
+- Include detailed docstrings explaining what's being integrated
+
+**** Integration Test File Structure
+Organize tests within each file using comment headers to group related scenarios:
+
+#+begin_src elisp
+;;; test-integration-recurring-events.el --- Integration tests for recurring events
+
+;;; Commentary:
+;; Integration tests covering the complete recurring event workflow:
+;; - Creating recurring events from Google Calendar API
+;; - Preserving timestamps across updates
+;; - Handling different recurrence patterns (WEEKLY, DAILY, etc.)
+;; - Managing recurrence metadata in org properties
+;;
+;; Components integrated: org-gcal--format-event-timestamp,
+;; org-gcal--update-entry, org-element-at-point
+
+;;; Code:
+
+(require 'org-gcal)
+(require 'ert)
+
+;; Test data constants
+(defconst test-integration-recurring-events-weekly-json ...)
+(defconst test-integration-recurring-events-daily-json ...)
+
+;; Helper functions
+(defun test-integration-recurring-events--json-read-string (json) ...)
+
+;;; Normal Cases - Recurring Event Creation
+
+(ert-deftest test-integration-recurring-events-weekly-creates-with-recurrence ()
+ "Test that weekly recurring event is created with recurrence property.
+
+Components integrated:
+- org-gcal--update-entry
+- org-gcal--format-event-timestamp
+- org-element-at-point"
+ ...)
+
+(ert-deftest test-integration-recurring-events-daily-creates-with-count ()
+ "Test that daily recurring event with COUNT creates correctly.
+
+Components integrated:
+- org-gcal--update-entry
+- org-gcal--format-event-timestamp"
+ ...)
+
+;;; Boundary Cases - Recurring Event Updates
+
+(ert-deftest test-integration-recurring-events-update-preserves-recurrence ()
+ "Test that updating recurring event preserves recurrence property.
+
+Components integrated:
+- org-gcal--update-entry (update path)
+- org-entry-get (property retrieval)"
+ ...)
+
+(ert-deftest test-integration-recurring-events-preserves-old-timestamps ()
+ "Test that recurring events preserve original timestamps across updates.
+
+This is the KEY test validating the refactored timestamp logic.
+
+Components integrated:
+- org-gcal--format-event-timestamp (with recurrence parameter)
+- org-gcal--update-entry (preserving old-start/old-end)
+- Full workflow: JSON → parsed data → formatted timestamp → org entry"
+ ...)
+
+;;; Edge Cases - Missing or Invalid Recurrence
+
+(ert-deftest test-integration-recurring-events-no-recurrence-uses-new-timestamps ()
+ "Test that events without recurrence use new timestamps on update.
+
+Components integrated:
+- org-gcal--format-event-timestamp (no recurrence path)
+- org-gcal--update-entry"
+ ...)
+
+(provide 'test-integration-recurring-events)
+;;; test-integration-recurring-events.el ends here
+#+end_src
+
+File structure guidelines:
+1. **Commentary section**: High-level overview of what's being integrated
+ - List the main workflow or feature
+ - Enumerate key components being tested together
+ - Explain the integration scope
+
+2. **Test data section**: Constants and fixtures
+ - Group related test data together
+ - Use descriptive constant names
+ - Document data format if non-obvious
+
+3. **Helper functions section**: Test utilities
+ - Functions used by multiple tests in this file
+ - Setup/teardown helpers
+ - Data transformation utilities
+
+4. **Grouped test sections**: Use comment headers to organize tests
+ - Start with `;;;` (three semicolons) for section headers
+ - Group by category: "Normal Cases", "Boundary Cases", "Edge Cases", "Error Cases"
+ - Or group by scenario: "Event Creation", "Event Updates", "Event Deletion"
+ - Or group by workflow stage: "Fetch Phase", "Update Phase", "Sync Phase"
+
+5. **Test ordering**: Organize tests logically
+ - Simple/common cases first
+ - Complex scenarios build on earlier tests
+ - Edge cases at the end
+ - Easier to understand test intent by reading top to bottom
+
+6. **Section headers should be discoverable**:
+ - Use grep-friendly patterns: `^;;;.*Cases` or `^;;; Test:`
+ - Consistent naming: always use "Normal/Boundary/Error Cases"
+ - Or use workflow stages consistently across files
+
+Benefits of grouping:
+- Easier to find related tests
+- Clear structure when file has 20+ tests
+- Documents test coverage patterns
+- Helps identify gaps (no error cases section? add some!)
+- Makes test maintenance easier
+- Improves test file readability
+
+**** Balancing Unit vs Integration Tests
+The testing pyramid:
+- **Base (most)**: Unit tests - Fast, isolated, granular
+- **Middle**: Integration tests - Realistic, component interactions
+- **Top (fewest)**: End-to-end tests - Full system, slowest
+
+For most projects:
+- 70-80% unit tests (individual functions)
+- 15-25% integration tests (component interactions)
+- 5-10% end-to-end tests (full workflows)
+
+Don't duplicate coverage:
+- If unit tests fully cover logic, integration tests focus on interactions
+- If integration test covers a workflow, don't repeat every unit test case
+- Integration tests validate unit-tested components work together correctly
+
*** Test Reviews
- Review tests with the same rigor as production code
- Check for proper assertions and failure messages
diff --git a/init.el b/init.el
index 0fdc3dec..b61c5ed5 100644
--- a/init.el
+++ b/init.el
@@ -29,7 +29,7 @@
(require 'custom-case) ;; operations for upper/lower/title case
(require 'custom-comments) ;; operations with comments
(require 'custom-datetime) ;; date/timestamp insertion in various formats
-(require 'custom-file-buffer) ;; custom buffer and file operations and keymap
+(require 'custom-buffer-file) ;; custom buffer and file operations and keymap
(require 'custom-line-paragraph) ;; operations on lines and paragraphs
(require 'custom-misc) ;; miscellaneous functions
(require 'custom-ordering) ;; ordering and sorting operations
@@ -110,7 +110,7 @@
(require 'org-contacts-config) ;; fully integrated org-mode contacts management
(require 'org-drill-config)
(require 'org-export-config)
-(require 'org-gcal-config)
+(require 'org-gcal-config) ;; bi directional sync google calendar for org-agenda
(require 'org-refile-config) ;; refile org-branches
(require 'org-roam-config) ;; personal knowledge management in org mode
(require 'org-webclipper) ;; "instapaper" to org-roam workflow
@@ -134,7 +134,7 @@
;; ------------------------- Personal Workflow Related -------------------------
(require 'reconcile-open-repos)
-(require 'local-repository)
+(require 'local-repository) ;; local repository for easy config portability
;; ------------------------------- Entertainment -------------------------------
@@ -144,8 +144,9 @@
;; ------------------------------ Modules In Test ------------------------------
;;(require 'wip)
-(require 'lorem-optimum)
-(require 'jumper)
+(require 'lorem-optimum) ;; best fake latin text generator ever
+(require 'jumper) ;; navigation help for large projects/lotsa buffers
+(require 'system-commands) ;; reboot, logout, etc.
;; ---------------------------------- Wrap Up ----------------------------------
diff --git a/modules/custom-file-buffer.el b/modules/custom-buffer-file.el
index 08f974fd..9438e8ed 100644
--- a/modules/custom-file-buffer.el
+++ b/modules/custom-buffer-file.el
@@ -1,4 +1,4 @@
-;;; custom-file-buffer.el --- Custom Buffer and File Operations -*- coding: utf-8; lexical-binding: t; -*-
+;;; custom-buffer-file.el --- Custom Buffer and File Operations -*- coding: utf-8; lexical-binding: t; -*-
;;
;;; Commentary:
;; This module provides custom buffer and file operations including PostScript
@@ -256,5 +256,5 @@ Do not save the deleted text in the kill ring."
"C-; b P" "copy file path"))
-(provide 'custom-file-buffer)
-;;; custom-file-buffer.el ends here.
+(provide 'custom-buffer-file)
+;;; custom-buffer-file.el ends here.
diff --git a/modules/mail-config.el b/modules/mail-config.el
index 402c2589..1d5a14ea 100644
--- a/modules/mail-config.el
+++ b/modules/mail-config.el
@@ -283,9 +283,9 @@ Prompts user for the action when executing."
;; user composes org mode; recipient receives html
(use-package org-msg
- :ensure nil ;; loading locally for fixes
+ ;; :vc (:url "https://github.com/cjennings/org-msg" :rev :newest)
+ :load-path "/home/cjennings/code/org-msg"
:defer 1
- :load-path "~/code/org-msg/"
:after (org mu4e)
:preface
(defvar-keymap cj/email-map
diff --git a/modules/org-agenda-config.el b/modules/org-agenda-config.el
index 7b436424..30f4606c 100644
--- a/modules/org-agenda-config.el
+++ b/modules/org-agenda-config.el
@@ -273,13 +273,25 @@ This allows a line to show in an agenda without being scheduled or a deadline."
;; This gives two notifications per event without any after-event notifications
(setq chime-alert-time '(5 0))
- ;; Modeline display: show upcoming events within 60 minutes
+ ;; Modeline display: show upcoming events within 2 hours
+ (setq chime-enable-modeline t)
(setq chime-modeline-lookahead 120)
(setq chime-modeline-format " ⏰ %s")
- ;; Chime sound: plays when notifications appear
- (setq chime-play-sound t)
- ;; Uses bundled chime.wav by default
+ ;; Modeline content: show title and countdown only (omit event time)
+ (setq chime-notification-text-format "%t (%u)")
+
+ ;; Time-until format: compact style like " in 10m" or " in 1h 37m"
+ (setq chime-time-left-format-short " in %mm") ; Under 1 hour: " in 10m"
+ (setq chime-time-left-format-long " in %hh %mm") ; 1 hour+: " in 1h 37m"
+ (setq chime-time-left-format-at-event "right now")
+
+ ;; Title truncation: limit long event titles to 15 characters
+ ;; This affects only the title, not the icon or countdown
+ (setq chime-max-title-length 25) ; "Very Long Me... ( in 10m)"
+
+ ;; Chime sound: disabled
+ (setq chime-play-sound nil)
;; Notification settings
(setq chime-notification-title "Reminder")
diff --git a/modules/org-gcal-config.el b/modules/org-gcal-config.el
index f3e1b7e0..b3c63663 100644
--- a/modules/org-gcal-config.el
+++ b/modules/org-gcal-config.el
@@ -38,6 +38,11 @@
(require 'host-environment)
(require 'user-constants)
+;; Forward declare org-gcal internal variables and functions
+(eval-when-compile
+ (defvar org-gcal--sync-lock))
+(declare-function org-gcal-reload-client-id-secret "org-gcal")
+
(defun cj/org-gcal-clear-sync-lock ()
"Clear the org-gcal sync lock.
Useful when a sync fails and leaves the lock in place, preventing future syncs."
@@ -48,7 +53,7 @@ Useful when a sync fails and leaves the lock in place, preventing future syncs."
(defun cj/org-gcal-convert-all-to-org-managed ()
"Convert all org-gcal events in current buffer to Org-managed.
-Changes all events with org-gcal-managed property from 'gcal' to 'org',
+Changes all events with org-gcal-managed property from `gcal' to `org',
enabling bidirectional sync so changes push back to Google Calendar."
(interactive)
(let ((count 0))
@@ -62,6 +67,7 @@ enabling bidirectional sync so changes push back to Google Calendar."
(message "Converted %d event(s) to Org-managed" count)))
(use-package org-gcal
+ :vc (:url "https://github.com/cjennings/org-gcal" :rev :newest)
:defer t ;; unless idle timer is set below
:bind (("C-; g" . org-gcal-sync)
("C-; G" . cj/org-gcal-clear-sync-lock))
@@ -99,11 +105,6 @@ enabling bidirectional sync so changes push back to Google Calendar."
(require 'plstore)
(setq plstore-cache-passphrase-for-symmetric-encryption t)
- ;; Enable debugging for HTTP requests
- (require 'request)
- (setq request-log-level 'debug)
- (setq request-message-level 'debug)
-
;; set org-gcal timezone based on system timezone
(setq org-gcal-local-timezone (cj/detect-system-timezone))
diff --git a/modules/org-roam-config.el b/modules/org-roam-config.el
index f78b68da..a6b42ce7 100644
--- a/modules/org-roam-config.el
+++ b/modules/org-roam-config.el
@@ -19,6 +19,7 @@
;; ---------------------------------- Org Roam ---------------------------------
(use-package org-roam
+ :defer 1
:commands (org-roam-node-find org-roam-node-insert org-roam-db-autosync-mode)
:config
;; Enable autosync mode after org-roam loads
diff --git a/modules/popper-config.el b/modules/popper-config.el
index b0f503e8..d9a9d9b0 100644
--- a/modules/popper-config.el
+++ b/modules/popper-config.el
@@ -26,6 +26,7 @@
'("\\*Messages\\*"
"Output\\*$"
"\\*Async Shell Command\\*"
+ "\\*Async-native-compile-log\\*"
help-mode
compilation-mode))
(add-to-list 'display-buffer-alist
diff --git a/modules/prog-general.el b/modules/prog-general.el
index 669922ef..d8d9627d 100644
--- a/modules/prog-general.el
+++ b/modules/prog-general.el
@@ -264,12 +264,8 @@ If no such file exists there, display a message."
("C-c s n" . yas-new-snippet)
("C-c s e" . yas-visit-snippet-file)
:config
- (setq yas-snippet-dirs '(snippets-dir)))
-
-(use-package ivy-yasnippet
- :after yasnippet
- :bind
- ("C-c s i" . ivy-yasnippet))
+ (setq yas-snippet-dirs (list snippets-dir))
+ (yas-reload-all))
;; --------------------- Display Color On Color Declaration --------------------
;; display the actual color as highlight to color hex code
diff --git a/modules/selection-framework.el b/modules/selection-framework.el
index 0bc71f64..a89afc02 100644
--- a/modules/selection-framework.el
+++ b/modules/selection-framework.el
@@ -27,7 +27,6 @@
(vertico-resize nil) ; Don't resize the minibuffer
(vertico-sort-function #'vertico-sort-history-alpha) ; History first, then alphabetical
:bind (:map vertico-map
- ;; Match ivy's C-j C-k behavior
("C-j" . vertico-next)
("C-k" . vertico-previous)
("C-l" . vertico-insert) ; Insert current candidate
@@ -128,7 +127,7 @@
;; Use Consult for completion-at-point
(setq completion-in-region-function #'consult-completion-in-region))
-(global-unset-key (kbd "C-s"))
+;; Override default search with consult-line
(keymap-global-set "C-s" #'consult-line)
;; Consult integration with Embark
@@ -152,10 +151,10 @@
(use-package orderless
:demand t
:custom
- (completion-styles '(orderless))
+ (completion-styles '(orderless basic))
(completion-category-defaults nil)
- (completion-category-overrides '((file (styles partial-completion))
- (multi-category (styles orderless))))
+ (completion-category-overrides '((file (styles partial-completion orderless basic))
+ (multi-category (styles orderless basic))))
(orderless-matching-styles '(orderless-literal
orderless-regexp
orderless-initialism
@@ -183,16 +182,10 @@
nil
(window-parameters (mode-line-format . none)))))
-;; this typo causes crashes
-;; (add-to-list 'display-buffer-alist
-;; '("\\=\\*Embark Collect \\(Live\\|Completions\\)\\*"
-;; nil
-;; (window-parameters (mode-line-format . none)))))
-
;; --------------------------- Consult Integration ----------------------------
;; Additional integrations for specific features
-;; Yasnippet integration - replaces ivy-yasnippet
+;; Yasnippet integration
(use-package consult-yasnippet
:after yasnippet
:bind ("C-c s i" . consult-yasnippet))
@@ -204,7 +197,7 @@
("C-c ! c" . consult-flycheck)))
;; ---------------------------------- Company ----------------------------------
-;; In-buffer completion (retained from original configuration)
+;; In-buffer completion for text and code
(use-package company
:demand t
@@ -261,7 +254,11 @@
;; which-key labels
(with-eval-after-load 'which-key
- (which-key-add-key-based-replacements "C-c h" "consult history"))
+ (which-key-add-key-based-replacements
+ "C-c h" "consult history"
+ "C-c s i" "insert snippet"
+ "M-g" "goto menu"
+ "M-s" "search menu"))
(provide 'selection-framework)
;;; selection-framework.el ends here
diff --git a/modules/system-commands.el b/modules/system-commands.el
new file mode 100644
index 00000000..fb8c0611
--- /dev/null
+++ b/modules/system-commands.el
@@ -0,0 +1,138 @@
+;;; system-commands.el --- System power and session management -*- lexical-binding: t; coding: utf-8; -*-
+;; author: Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;;
+;; System commands for logout, lock, suspend, shutdown, reboot, and Emacs
+;; exit/restart. Provides both a keymap (C-; !) and a completing-read menu.
+;;
+;; Commands include:
+;; - Logout (terminate user session)
+;; - Lock screen (slock)
+;; - Suspend (systemctl suspend)
+;; - Shutdown (systemctl poweroff)
+;; - Reboot (systemctl reboot)
+;; - Exit Emacs (kill-emacs)
+;; - Restart Emacs (via systemctl --user restart emacs.service)
+;;
+;; Dangerous commands (logout, suspend, shutdown, reboot) require confirmation.
+;;
+;;; Code:
+
+(eval-when-compile (require 'keybindings))
+(eval-when-compile (require 'subr-x))
+(require 'rx)
+
+;; ------------------------------ System Commands ------------------------------
+
+(defun cj/system-cmd--resolve (cmd)
+ "Return (values symbol-or-nil command-string label) for CMD."
+ (cond
+ ((symbolp cmd)
+ (let ((val (and (boundp cmd) (symbol-value cmd))))
+ (unless (and (stringp val) (not (string-empty-p val)))
+ (user-error "Variable %s is not a non-empty string" cmd))
+ (list cmd val (symbol-name cmd))))
+ ((stringp cmd)
+ (let ((s (string-trim cmd)))
+ (when (string-empty-p s) (user-error "Command string is empty"))
+ (list nil s "command")))
+ (t (user-error "Error: cj/system-cmd expects a string or a symbol"))))
+
+(defun cj/system-cmd (cmd)
+ "Run CMD (string or symbol naming a string) detached via the shell.
+Shell expansions like $(...) are supported. Output is silenced.
+If CMD is deemed dangerous, ask for confirmation."
+ (interactive (list (read-shell-command "System command: ")))
+ (pcase-let ((`(,sym ,cmdstr ,label) (cj/system-cmd--resolve cmd)))
+ (when (and sym (get sym 'cj/system-confirm)
+ (memq (read-char-choice
+ (format "Run %s now (%s)? (Y/n) " label cmdstr)
+ '(?y ?Y ?n ?N ?\r ?\n ?\s))
+ '(?n ?N)))
+ (user-error "Aborted"))
+ (let ((proc (start-process-shell-command "cj/system-cmd" nil
+ (format "nohup %s >/dev/null 2>&1 &" cmdstr))))
+ (set-process-query-on-exit-flag proc nil)
+ (set-process-sentinel proc #'ignore)
+ (message "Running %s..." label))))
+
+(defmacro cj/defsystem-command (name var cmdstr &optional confirm)
+ "Define VAR with CMDSTR and interactive command NAME to run it.
+If CONFIRM is non-nil, mark VAR to always require confirmation."
+ (declare (indent defun))
+ `(progn
+ (defvar ,var ,cmdstr)
+ ,(when confirm `(put ',var 'cj/system-confirm t))
+ (defun ,name ()
+ ,(format "Run %s via `cj/system-cmd'." var)
+ (interactive)
+ (cj/system-cmd ',var))))
+
+;; Define system commands
+(cj/defsystem-command cj/system-cmd-logout logout-cmd "loginctl terminate-user $(whoami)" t)
+(cj/defsystem-command cj/system-cmd-lock lockscreen-cmd "slock")
+(cj/defsystem-command cj/system-cmd-suspend suspend-cmd "systemctl suspend" t)
+(cj/defsystem-command cj/system-cmd-shutdown shutdown-cmd "systemctl poweroff" t)
+(cj/defsystem-command cj/system-cmd-reboot reboot-cmd "systemctl reboot" t)
+
+(defun cj/system-cmd-exit-emacs ()
+ "Exit Emacs server and all clients."
+ (interactive)
+ (when (memq (read-char-choice
+ "Exit Emacs? (Y/n) "
+ '(?y ?Y ?n ?N ?\r ?\n ?\s))
+ '(?n ?N))
+ (user-error "Aborted"))
+ (kill-emacs))
+
+(defun cj/system-cmd-restart-emacs ()
+ "Restart Emacs server after saving buffers."
+ (interactive)
+ (when (memq (read-char-choice
+ "Restart Emacs? (Y/n) "
+ '(?y ?Y ?n ?N ?\r ?\n ?\s))
+ '(?n ?N))
+ (user-error "Aborted"))
+ (save-some-buffers)
+ ;; Start the restart process before killing Emacs
+ (run-at-time 0.5 nil
+ (lambda ()
+ (call-process-shell-command
+ "systemctl --user restart emacs.service && emacsclient -c"
+ nil 0)))
+ (run-at-time 1 nil #'kill-emacs)
+ (message "Restarting Emacs..."))
+
+(defvar-keymap cj/system-command-map
+ :doc "Keymap for system commands."
+ "L" #'cj/system-cmd-logout
+ "r" #'cj/system-cmd-reboot
+ "s" #'cj/system-cmd-shutdown
+ "S" #'cj/system-cmd-suspend
+ "l" #'cj/system-cmd-lock
+ "E" #'cj/system-cmd-exit-emacs
+ "e" #'cj/system-cmd-restart-emacs)
+(keymap-set cj/custom-keymap "!" cj/system-command-map)
+
+(defun cj/system-command-menu ()
+ "Present system commands via \='completing-read\='."
+ (interactive)
+ (let* ((commands '(("Logout System" . cj/system-cmd-logout)
+ ("Lock Screen" . cj/system-cmd-lock)
+ ("Suspend System" . cj/system-cmd-suspend)
+ ("Shutdown System" . cj/system-cmd-shutdown)
+ ("Reboot System" . cj/system-cmd-reboot)
+ ("Exit Emacs" . cj/system-cmd-exit-emacs)
+ ("Restart Emacs" . cj/system-cmd-restart-emacs)))
+ (choice (completing-read "System command: " commands nil t)))
+ (when-let ((cmd (alist-get choice commands nil nil #'equal)))
+ (call-interactively cmd))))
+
+(keymap-set cj/custom-keymap "!" #'cj/system-command-menu)
+
+(with-eval-after-load 'which-key
+ (which-key-add-key-based-replacements "C-; !" "system commands"))
+
+(provide 'system-commands)
+;;; system-commands.el ends here
diff --git a/modules/weather-config.el b/modules/weather-config.el
index 526a0b41..31fb1b70 100644
--- a/modules/weather-config.el
+++ b/modules/weather-config.el
@@ -11,9 +11,8 @@
;; ----------------------------------- Wttrin ----------------------------------
(use-package wttrin
+ :vc (:url "https://github.com/cjennings/emacs-wttrin" :rev :newest)
:defer t
- :load-path ("~/code/wttrin")
- :ensure nil ;; local package
:preface
;; dependency for wttrin
(use-package xterm-color
diff --git a/modules/wip.el b/modules/wip.el
index db94cdb1..93c799fb 100644
--- a/modules/wip.el
+++ b/modules/wip.el
@@ -14,135 +14,6 @@
;;
;;; Code:
-(eval-when-compile (require 'user-constants))
-(eval-when-compile (require 'keybindings))
-(eval-when-compile (require 'subr-x)) ;; for system commands
-(require 'rx) ;; for system commands
-
-;; ------------------------------ System Commands ------------------------------
-
-(defun cj/system-cmd--resolve (cmd)
- "Return (values symbol-or-nil command-string label) for CMD."
- (cond
- ((symbolp cmd)
- (let ((val (and (boundp cmd) (symbol-value cmd))))
- (unless (and (stringp val) (not (string-empty-p val)))
- (user-error "Variable %s is not a non-empty string" cmd))
- (list cmd val (symbol-name cmd))))
- ((stringp cmd)
- (let ((s (string-trim cmd)))
- (when (string-empty-p s) (user-error "Command string is empty"))
- (list nil s "command")))
- (t (user-error "Error: cj/system-cmd expects a string or a symbol"))))
-
-(defun cj/system-cmd (cmd)
- "Run CMD (string or symbol naming a string) detached via the shell.
-Shell expansions like $(...) are supported. Output is silenced.
-If CMD is deemed dangerous, ask for confirmation."
- (interactive (list (read-shell-command "System command: ")))
- (pcase-let ((`(,sym ,cmdstr ,label) (cj/system-cmd--resolve cmd)))
- (when (and sym (get sym 'cj/system-confirm)
- (memq (read-char-choice
- (format "Run %s now (%s)? (Y/n) " label camdstr)
- '(?y ?Y ?n ?N ?\r ?\n ?\s))
- '(?n ?N)))
- (user-error "Aborted"))
- (let ((proc (start-process-shell-command "cj/system-cmd" nil
- (format "nohup %s >/dev/null 2>&1 &" cmdstr))))
- (set-process-query-on-exit-flag proc nil)
- (set-process-sentinel proc #'ignore)
- (message "Running %s..." label))))
-
-(defmacro cj/defsystem-command (name var cmdstr &optional confirm)
- "Define VAR with CMDSTR and interactive command NAME to run it.
-If CONFIRM is non-nil, mark VAR to always require confirmation."
- (declare (indent defun))
- `(progn
- (defvar ,var ,cmdstr)
- ,(when confirm `(put ',var 'cj/system-confirm t))
- (defun ,name ()
- ,(format "Run %s via `cj/system-cmd'." var)
- (interactive)
- (cj/system-cmd ',var))))
-
-;; Define system commands
-(cj/defsystem-command cj/system-cmd-logout logout-cmd "loginctl terminate-user $(whoami)" t)
-(cj/defsystem-command cj/system-cmd-lock lockscreen-cmd "slock")
-(cj/defsystem-command cj/system-cmd-suspend suspend-cmd "systemctl suspend" t)
-(cj/defsystem-command cj/system-cmd-shutdown shutdown-cmd "systemctl poweroff" t)
-(cj/defsystem-command cj/system-cmd-reboot reboot-cmd "systemctl reboot" t)
-
-(defun cj/system-cmd-exit-emacs ()
- "Exit Emacs server and all clients."
- (interactive)
- (when (memq (read-char-choice
- "Exit Emacs? (Y/n) "
- '(?y ?Y ?n ?N ?\r ?\n ?\s))
- '(?n ?N))
- (user-error "Aborted"))
- (kill-emacs))
-
-(defun cj/system-cmd-restart-emacs ()
- "Restart Emacs server after saving buffers."
- (interactive)
- (when (memq (read-char-choice
- "Restart Emacs? (Y/n) "
- '(?y ?Y ?n ?N ?\r ?\n ?\s))
- '(?n ?N))
- (user-error "Aborted"))
- (save-some-buffers)
- ;; Start the restart process before killing Emacs
- (run-at-time 0.5 nil
- (lambda ()
- (call-process-shell-command
- "systemctl --user restart emacs.service && emacsclient -c"
- nil 0)))
- (run-at-time 1 nil #'kill-emacs)
- (message "Restarting Emacs..."))
-
-;; (defvar-keymap cj/system-command-map
-;; :doc "Keymap for system commands."
-;; "L" #'cj/system-cmd-logout
-;; "r" #'cj/system-cmd-reboot
-;; "s" #'cj/system-cmd-shutdown
-;; "S" #'cj/system-cmd-suspend
-;; "l" #'cj/system-cmd-lock
-;; "E" #'cj/system-cmd-exit-emacs
-;; "e" #'cj/system-cmd-restart-emacs)
-;; (keymap-set cj/custom-keymap "!" cj/system-command-map)
-
-(defun cj/system-command-menu ()
- "Present system commands via \='completing-read\='."
- (interactive)
- (let* ((commands '(("Logout System" . cj/system-cmd-logout)
- ("Lock Screen" . cj/system-cmd-lock)
- ("Suspend System" . cj/system-cmd-suspend)
- ("Shutdown System" . cj/system-cmd-shutdown)
- ("Reboot System" . cj/system-cmd-reboot)
- ("Exit Emacs" . cj/system-cmd-exit-emacs)
- ("Restart Emacs" . cj/system-cmd-restart-emacs)))
- (choice (completing-read "System command: " commands nil t)))
- (when-let ((cmd (alist-get choice commands nil nil #'equal)))
- (call-interactively cmd))))
-
-(keymap-set cj/custom-keymap "!" #'cj/system-command-menu)
-
-(with-eval-after-load 'which-key
- (which-key-add-key-based-replacements "C-; !" "system commands"))
-
-;; --------------------------- Org Upcoming Modeline ---------------------------
-
-;; (use-package org-upcoming-modeline
-;; :after org
-;; :load-path "~/code/org-upcoming-modeline/org-upcoming-modeline.el"
-;; :config
-;; (setq org-upcoming-modeline-keep-late 300)
-;; (setq org-upcoming-modeline-ignored-keywords '("DONE" "CANCELLED" "FAILED"))
-;; (setq org-upcoming-modeline-trim 30)
-;; (setq org-upcoming-modeline-days-ahead 5)
-;; (setq org-upcoming-modeline-format (lambda (ms mh) (format "📅 %s %s" ms mh)))
-;; (org-upcoming-modeline-mode))
-
;; ----------------------------------- Efrit -----------------------------------
;; not working as of Wednesday, September 03, 2025 at 12:44:09 AM CDT
@@ -185,30 +56,5 @@ If CONFIRM is non-nil, mark VAR to always require confirmation."
:bind ("M-p" . pomm)
:commands (pomm pomm-third-time))
-;; ----------------------------------- Popper ----------------------------------
-
-;; (use-package popper
-;; :bind (("C-`" . popper-toggle)
-;; ("M-`" . popper-cycle)
-;; ("C-M-`" . popper-toggle-type))
-;; :custom
-;; (popper-display-control-nil)
-;; :init
-;; (setq popper-reference-buffers
-;; '("\\*Messages\\*"
-;; "Output\\*$"
-;; "\\*Async Shell Command\\*"
-;; ;; "\\*scratch\\*"
-;; help-mode
-;; compilation-mode))
-;; (add-to-list 'display-buffer-alist
-;; '(popper-display-control-p ; Predicate to match popper buffers
-;; (display-buffer-in-side-window)
-;; (side . bottom)
-;; (slot . 0)
-;; (window-height . 0.5))) ; Half the frame height
-;; (popper-mode +1)
-;; (popper-echo-mode +1))
-
(provide 'wip)
;;; wip.el ends here.
diff --git a/tests/test-custom-file-buffer-clear-to-bottom-of-buffer.el b/tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el
index 969f9bb7..bd309880 100644
--- a/tests/test-custom-file-buffer-clear-to-bottom-of-buffer.el
+++ b/tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-clear-to-bottom-of-buffer.el --- Tests for cj/clear-to-bottom-of-buffer -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-clear-to-bottom-of-buffer.el --- Tests for cj/clear-to-bottom-of-buffer -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/clear-to-bottom-of-buffer function from custom-file-buffer.el
+;; Tests for the cj/clear-to-bottom-of-buffer function from custom-buffer-file.el
;;
;; This function deletes all text from point to the end of the current buffer.
;; It does not save the deleted text in the kill ring.
@@ -22,7 +22,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -159,5 +159,5 @@
(should-error (cj/clear-to-bottom-of-buffer)))
(test-clear-to-bottom-teardown)))
-(provide 'test-custom-file-buffer-clear-to-bottom-of-buffer)
-;;; test-custom-file-buffer-clear-to-bottom-of-buffer.el ends here
+(provide 'test-custom-buffer-file-clear-to-bottom-of-buffer)
+;;; test-custom-buffer-file-clear-to-bottom-of-buffer.el ends here
diff --git a/tests/test-custom-file-buffer-clear-to-top-of-buffer.el b/tests/test-custom-buffer-file-clear-to-top-of-buffer.el
index 18e3f71b..2bf79b27 100644
--- a/tests/test-custom-file-buffer-clear-to-top-of-buffer.el
+++ b/tests/test-custom-buffer-file-clear-to-top-of-buffer.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-clear-to-top-of-buffer.el --- Tests for cj/clear-to-top-of-buffer -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-clear-to-top-of-buffer.el --- Tests for cj/clear-to-top-of-buffer -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/clear-to-top-of-buffer function from custom-file-buffer.el
+;; Tests for the cj/clear-to-top-of-buffer function from custom-buffer-file.el
;;
;; This function deletes all text from point to the beginning of the current buffer.
;; It does not save the deleted text in the kill ring.
@@ -22,7 +22,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -158,5 +158,5 @@
(should-error (cj/clear-to-top-of-buffer)))
(test-clear-to-top-teardown)))
-(provide 'test-custom-file-buffer-clear-to-top-of-buffer)
-;;; test-custom-file-buffer-clear-to-top-of-buffer.el ends here
+(provide 'test-custom-buffer-file-clear-to-top-of-buffer)
+;;; test-custom-buffer-file-clear-to-top-of-buffer.el ends here
diff --git a/tests/test-custom-file-buffer-copy-link-to-buffer-file.el b/tests/test-custom-buffer-file-copy-link-to-buffer-file.el
index 94d1e01e..262968d6 100644
--- a/tests/test-custom-file-buffer-copy-link-to-buffer-file.el
+++ b/tests/test-custom-buffer-file-copy-link-to-buffer-file.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-copy-link-to-buffer-file.el --- Tests for cj/copy-link-to-buffer-file -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-copy-link-to-buffer-file.el --- Tests for cj/copy-link-to-buffer-file -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/copy-link-to-buffer-file function from custom-file-buffer.el
+;; Tests for the cj/copy-link-to-buffer-file function from custom-buffer-file.el
;;
;; This function copies the full file:// path of the current buffer's file to
;; the kill ring. For non-file buffers, it does nothing (no error).
@@ -22,7 +22,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -205,5 +205,5 @@
(should (null kill-ring))))
(test-copy-link-teardown)))
-(provide 'test-custom-file-buffer-copy-link-to-buffer-file)
-;;; test-custom-file-buffer-copy-link-to-buffer-file.el ends here
+(provide 'test-custom-buffer-file-copy-link-to-buffer-file)
+;;; test-custom-buffer-file-copy-link-to-buffer-file.el ends here
diff --git a/tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el b/tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el
index e7a6f64b..08959a85 100644
--- a/tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el
+++ b/tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el --- Tests for cj/copy-path-to-buffer-file-as-kill -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el --- Tests for cj/copy-path-to-buffer-file-as-kill -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/copy-path-to-buffer-file-as-kill function from custom-file-buffer.el
+;; Tests for the cj/copy-path-to-buffer-file-as-kill function from custom-buffer-file.el
;;
;; This function copies the full path of the current buffer's file to the kill ring
;; and returns the path. It signals an error if the buffer is not visiting a file.
@@ -22,7 +22,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -201,5 +201,5 @@
(should-error (cj/copy-path-to-buffer-file-as-kill) :type 'user-error))
(test-copy-path-teardown)))
-(provide 'test-custom-file-buffer-copy-path-to-buffer-file-as-kill)
-;;; test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el ends here
+(provide 'test-custom-buffer-file-copy-path-to-buffer-file-as-kill)
+;;; test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el ends here
diff --git a/tests/test-custom-file-buffer-copy-whole-buffer.el b/tests/test-custom-buffer-file-copy-whole-buffer.el
index a0546b18..181c491a 100644
--- a/tests/test-custom-file-buffer-copy-whole-buffer.el
+++ b/tests/test-custom-buffer-file-copy-whole-buffer.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-copy-whole-buffer.el --- Tests for cj/copy-whole-buffer -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-copy-whole-buffer.el --- Tests for cj/copy-whole-buffer -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/copy-whole-buffer function from custom-file-buffer.el
+;; Tests for the cj/copy-whole-buffer function from custom-buffer-file.el
;;
;; This function copies the entire contents of the current buffer to the kill ring.
;; Point and mark are left exactly where they were. No transient region is created.
@@ -22,7 +22,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -190,5 +190,5 @@
(should (null (text-properties-at 0 (car kill-ring)))))
(test-copy-whole-buffer-teardown)))
-(provide 'test-custom-file-buffer-copy-whole-buffer)
-;;; test-custom-file-buffer-copy-whole-buffer.el ends here
+(provide 'test-custom-buffer-file-copy-whole-buffer)
+;;; test-custom-buffer-file-copy-whole-buffer.el ends here
diff --git a/tests/test-custom-file-buffer-delete-buffer-and-file.el b/tests/test-custom-buffer-file-delete-buffer-and-file.el
index 1c43ff3b..4af8d2a7 100644
--- a/tests/test-custom-file-buffer-delete-buffer-and-file.el
+++ b/tests/test-custom-buffer-file-delete-buffer-and-file.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-delete-buffer-and-file.el --- Tests for cj/delete-buffer-and-file -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-delete-buffer-and-file.el --- Tests for cj/delete-buffer-and-file -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/delete-buffer-and-file function from custom-file-buffer.el
+;; Tests for the cj/delete-buffer-and-file function from custom-buffer-file.el
;;
;; This function deletes both the current buffer and the file it visits.
;; It uses vc-delete-file for version-controlled files and delete-file
@@ -42,7 +42,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -667,5 +667,5 @@
(should-error (cj/delete-buffer-and-file))))
(test-delete-buffer-and-file-teardown)))
-(provide 'test-custom-file-buffer-delete-buffer-and-file)
-;;; test-custom-file-buffer-delete-buffer-and-file.el ends here
+(provide 'test-custom-buffer-file-delete-buffer-and-file)
+;;; test-custom-buffer-file-delete-buffer-and-file.el ends here
diff --git a/tests/test-custom-file-buffer-move-buffer-and-file.el b/tests/test-custom-buffer-file-move-buffer-and-file.el
index 1fc16011..e8f4563d 100644
--- a/tests/test-custom-file-buffer-move-buffer-and-file.el
+++ b/tests/test-custom-buffer-file-move-buffer-and-file.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-move-buffer-and-file.el --- Tests for cj/move-buffer-and-file -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-move-buffer-and-file.el --- Tests for cj/move-buffer-and-file -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/--move-buffer-and-file function from custom-file-buffer.el
+;; Tests for the cj/--move-buffer-and-file function from custom-buffer-file.el
;;
;; This is the internal (non-interactive) implementation that moves both the
;; current buffer and its visited file to a new directory. It handles trailing
@@ -25,7 +25,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -932,5 +932,5 @@
(kill-buffer (current-buffer)))
(test-move-buffer-and-file-teardown)))
-(provide 'test-custom-file-buffer-move-buffer-and-file)
-;;; test-custom-file-buffer-move-buffer-and-file.el ends here
+(provide 'test-custom-buffer-file-move-buffer-and-file)
+;;; test-custom-buffer-file-move-buffer-and-file.el ends here
diff --git a/tests/test-custom-file-buffer-rename-buffer-and-file.el b/tests/test-custom-buffer-file-rename-buffer-and-file.el
index ca8acff8..1eb61f1b 100644
--- a/tests/test-custom-file-buffer-rename-buffer-and-file.el
+++ b/tests/test-custom-buffer-file-rename-buffer-and-file.el
@@ -1,7 +1,7 @@
-;;; test-custom-file-buffer-rename-buffer-and-file.el --- Tests for cj/--rename-buffer-and-file -*- lexical-binding: t; -*-
+;;; test-custom-buffer-file-rename-buffer-and-file.el --- Tests for cj/--rename-buffer-and-file -*- lexical-binding: t; -*-
;;; Commentary:
-;; Tests for the cj/--rename-buffer-and-file function from custom-file-buffer.el
+;; Tests for the cj/--rename-buffer-and-file function from custom-buffer-file.el
;;
;; This is the internal (non-interactive) implementation that renames both the
;; current buffer and its visited file. The interactive wrapper
@@ -24,7 +24,7 @@
(provide 'ps-print)
;; Now load the actual production module
-(require 'custom-file-buffer)
+(require 'custom-buffer-file)
;;; Setup and Teardown
@@ -935,5 +935,5 @@
(kill-buffer (current-buffer)))
(test-rename-buffer-and-file-teardown)))
-(provide 'test-custom-file-buffer-rename-buffer-and-file)
-;;; test-custom-file-buffer-rename-buffer-and-file.el ends here
+(provide 'test-custom-buffer-file-rename-buffer-and-file)
+;;; test-custom-buffer-file-rename-buffer-and-file.el ends here
diff --git a/tests/test-org-gcal-mock.el b/tests/test-org-gcal-mock.el
new file mode 100644
index 00000000..4b063867
--- /dev/null
+++ b/tests/test-org-gcal-mock.el
@@ -0,0 +1,112 @@
+;;; test-org-gcal-mock.el --- Mock test for org-gcal sync -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Mock test to capture what org-gcal sends to Google Calendar API
+;; This helps debug bidirectional sync issues without hitting the real API
+
+;;; Code:
+
+(require 'ert)
+(require 'org)
+
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Load org-gcal (this will require auth, but we'll mock the requests)
+(require 'org-gcal-config nil t)
+
+;; Variables to capture requests
+(defvar test-org-gcal-captured-requests nil
+ "List of captured HTTP requests.")
+
+(defvar test-org-gcal-captured-url nil
+ "Last captured URL.")
+
+(defvar test-org-gcal-captured-type nil
+ "Last captured HTTP method (GET/POST/PUT/PATCH).")
+
+(defvar test-org-gcal-captured-data nil
+ "Last captured request body/data.")
+
+(defvar test-org-gcal-captured-headers nil
+ "Last captured request headers.")
+
+;;; Mock request-deferred to capture what org-gcal sends
+
+(defun test-org-gcal-mock-request-deferred (url &rest args)
+ "Mock request-deferred to capture requests instead of sending them.
+URL is the API endpoint. ARGS contains :type, :data, :headers, etc."
+ (let* ((type (plist-get args :type))
+ (data (plist-get args :data))
+ (headers (plist-get args :headers)))
+ ;; Capture the request
+ (setq test-org-gcal-captured-url url)
+ (setq test-org-gcal-captured-type type)
+ (setq test-org-gcal-captured-data data)
+ (setq test-org-gcal-captured-headers headers)
+ (push (list :url url
+ :type type
+ :data data
+ :headers headers)
+ test-org-gcal-captured-requests)
+
+ ;; Print for debugging
+ (message "MOCK REQUEST: %s %s" type url)
+ (when data
+ (message "MOCK DATA: %S" data))
+
+ ;; Return a mock deferred object that succeeds immediately
+ (require 'deferred)
+ (deferred:succeed
+ ;; Mock response with a fake event
+ (list :data '(:id "test-event-id-123"
+ :etag "test-etag-456"
+ :summary "Test Event"
+ :start (:dateTime "2025-10-28T14:00:00-05:00")
+ :end (:dateTime "2025-10-28T15:00:00-05:00"))
+ :status-code 200))))
+
+(ert-deftest test-org-gcal-capture-post-request ()
+ "Test capturing what org-gcal sends when posting an event."
+ ;; Reset captured requests
+ (setq test-org-gcal-captured-requests nil)
+ (setq test-org-gcal-captured-url nil)
+ (setq test-org-gcal-captured-type nil)
+ (setq test-org-gcal-captured-data nil)
+
+ ;; Mock request-deferred
+ (cl-letf (((symbol-function 'request-deferred) #'test-org-gcal-mock-request-deferred))
+
+ ;; Create a test org buffer with an event
+ (with-temp-buffer
+ (org-mode)
+ (insert "* TEST: Mock Sync Test Event\n")
+ (insert "<2025-10-28 Tue 14:00-15:00>\n")
+ (insert "\n")
+ (insert "Test event for mocking.\n")
+
+ ;; Go to the headline
+ (goto-char (point-min))
+ (org-back-to-heading)
+
+ ;; Try to post (this should be captured by our mock)
+ (condition-case err
+ (org-gcal-post-at-point)
+ (error
+ (message "Error during post: %S" err)))))
+
+ ;; Check what was captured
+ (should test-org-gcal-captured-requests)
+ (let ((request (car test-org-gcal-captured-requests)))
+ (message "Captured URL: %s" (plist-get request :url))
+ (message "Captured Type: %s" (plist-get request :type))
+ (message "Captured Data: %S" (plist-get request :data))
+
+ ;; Verify it's trying to POST/PATCH
+ (should (member (plist-get request :type) '("POST" "PATCH" "PUT")))
+
+ ;; Verify URL contains calendar API
+ (should (string-match-p "googleapis.com/calendar" (plist-get request :url)))))
+
+(provide 'test-org-gcal-mock)
+;;; test-org-gcal-mock.el ends here