Skip to content

Commit f374199

Browse files
authored
Merge pull request #538 from mikegreiner/feature/issue-280-frontmatter-exists
Fix #280: Add searchType: frontmatter.exists for tracking field existence
2 parents 7517b66 + 9ea538b commit f374199

53 files changed

Lines changed: 729 additions & 18 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,27 @@ From version 1.9.0, template variables, e.g. '{{sum}}', are deprecated. Instead,
146146

147147
For more use cases, please download and open the [examples](https://github.com/pyrochlore/obsidian-tracker/tree/master/examples) folder in obsidian with this plugin installed and enabled.
148148

149+
## Development
150+
151+
### Running Tests
152+
153+
The plugin includes automated unit tests. To run them:
154+
155+
```bash
156+
npm test # Run all tests
157+
npm run test:watch # Watch mode (re-runs on changes)
158+
npm run test:coverage # Generate coverage report
159+
```
160+
161+
See [TESTING.md](TESTING.md) for detailed testing documentation.
162+
163+
### Building
164+
165+
```bash
166+
npm run build # Build for production
167+
npm run dev # Build in watch mode for development
168+
```
169+
149170
## More Details You May Want to Know
150171

151172
- [Installation](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Installation.md): Install the plugin from Obsidian or install it manually
@@ -158,6 +179,7 @@ For more use cases, please download and open the [examples](https://github.com/p
158179
- [Release Notes](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/ReleaseNotes.md)
159180
- [Road Map](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/RoadMap.md)
160181
- [Frequently Asked Questions](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/Questions.md)
182+
- [Testing](TESTING.md): How to run and write tests
161183

162184
## Support
163185

TESTING.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Testing Guide
2+
3+
This document explains how to run tests for the obsidian-tracker plugin.
4+
5+
## Quick Start
6+
7+
```bash
8+
npm test
9+
```
10+
11+
That's it! The tests will run automatically.
12+
13+
## Test Commands
14+
15+
```bash
16+
npm test # Run all tests once
17+
npm run test:watch # Run tests in watch mode (re-runs on file changes)
18+
npm run test:coverage # Run tests with coverage report
19+
```
20+
21+
## What Gets Tested
22+
23+
### Unit Tests
24+
25+
Unit tests validate core logic without requiring Obsidian to be running. They test:
26+
27+
- **Data collection functions** - How data is extracted from frontmatter, tags, etc.
28+
- **Parsing functions** - YAML configuration parsing and validation
29+
- **Edge cases** - Empty values, null, undefined, arrays, booleans, etc.
30+
31+
### Current Test Coverage
32+
33+
- `test/frontmatter-exists.test.ts` - Tests for the `frontmatter.exists` searchType
34+
- 12 test cases covering all edge cases
35+
- Validates non-empty strings, arrays, booleans, numbers
36+
- Validates empty strings, arrays, null, undefined are rejected
37+
38+
## Test Structure
39+
40+
```
41+
test/
42+
├── frontmatter-exists.test.ts # Unit tests for frontmatter.exists
43+
├── mocks/
44+
│ ├── obsidian.ts # Mock Obsidian API
45+
│ └── d3.ts # Mock d3 library
46+
└── setup.ts # Jest setup file
47+
```
48+
49+
## Prerequisites
50+
51+
1. **Node.js** - Version 18+ recommended
52+
2. **npm** - Comes with Node.js
53+
3. **Dependencies** - Run `npm install` first
54+
55+
## First Time Setup
56+
57+
```bash
58+
# Install dependencies (including Jest)
59+
npm install
60+
61+
# Verify tests work
62+
npm test
63+
```
64+
65+
If you encounter issues with npm install (e.g., obsidian package integrity errors), see [Troubleshooting](#troubleshooting) below.
66+
67+
## Writing New Tests
68+
69+
### Example Test Structure
70+
71+
```typescript
72+
import { describe, it, expect, beforeEach } from '@jest/globals';
73+
import { SearchType, Query } from '../src/data';
74+
import { yourFunction } from '../src/your-module';
75+
76+
describe('Your Feature', () => {
77+
let query: Query;
78+
79+
beforeEach(() => {
80+
// Set up test data
81+
query = new Query(0, SearchType.YourType, 'target');
82+
});
83+
84+
it('should do something', () => {
85+
const result = yourFunction(query);
86+
expect(result).toBe(true);
87+
});
88+
});
89+
```
90+
91+
### Test File Naming
92+
93+
- Test files should be named `*.test.ts` or `*.spec.ts`
94+
- Place them in the `test/` directory
95+
- Jest will automatically find and run them
96+
97+
## Mocking Obsidian API
98+
99+
Since tests run outside Obsidian, we mock the Obsidian API. See `test/mocks/obsidian.ts` for examples.
100+
101+
To use mocks:
102+
103+
```typescript
104+
import type { CachedMetadata } from 'obsidian';
105+
106+
const fileCache: CachedMetadata = {
107+
frontmatter: {
108+
field: 'value'
109+
}
110+
};
111+
```
112+
113+
## Troubleshooting
114+
115+
### npm install fails with obsidian package error
116+
117+
If you see integrity check errors for the obsidian package:
118+
119+
```bash
120+
# Clear npm cache
121+
npm cache clean --force
122+
123+
# Remove node_modules and reinstall
124+
rm -rf node_modules package-lock.json
125+
npm install
126+
```
127+
128+
### Tests fail with "Module not found"
129+
130+
Make sure all dependencies are installed:
131+
132+
```bash
133+
npm install
134+
```
135+
136+
### TypeScript errors in tests
137+
138+
Check that `ts-jest` is installed:
139+
140+
```bash
141+
npm install --save-dev ts-jest@29 @types/jest@29
142+
```
143+
144+
## Continuous Integration
145+
146+
These tests can be run in CI/CD pipelines:
147+
148+
```yaml
149+
# Example GitHub Actions
150+
- name: Run tests
151+
run: npm test
152+
```
153+
154+
## Manual Testing (Obsidian)
155+
156+
For visual/end-to-end testing, you still need to test in Obsidian:
157+
158+
1. Build the plugin: `npm run build`
159+
2. Copy to test vault: See `test-deploy.sh` or `TEST_SETUP.md`
160+
3. Enable plugin in Obsidian
161+
4. Test with real notes
162+
163+
See `docs/dev/issue-497/TESTING_GUIDE.md` for detailed manual testing instructions.
164+
165+
## Coverage Reports
166+
167+
Generate coverage reports:
168+
169+
```bash
170+
npm run test:coverage
171+
```
172+
173+
This creates a `coverage/` directory with HTML reports. Open `coverage/lcov-report/index.html` in a browser to view.
174+
175+
## Best Practices
176+
177+
1. **Write tests for new features** - Add tests when implementing new searchTypes or features
178+
2. **Test edge cases** - Empty values, null, undefined, arrays, etc.
179+
3. **Keep tests fast** - Unit tests should run in seconds
180+
4. **Mock external dependencies** - Don't require Obsidian or real files
181+
5. **Use descriptive test names** - "should count non-empty strings" not "test1"
182+
183+
## Questions?
184+
185+
- See `TEST_SETUP.md` for setup troubleshooting
186+
- Check existing test files for examples
187+
- Review Jest documentation: https://jestjs.io/docs/getting-started

TEST_SETUP.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Test Setup Guide
2+
3+
## Quick Start
4+
5+
Tests are now set up and working! Just run:
6+
7+
```bash
8+
npm test
9+
```
10+
11+
## First Time Setup
12+
13+
If you're setting up the project for the first time:
14+
15+
```bash
16+
# Install all dependencies (including Jest)
17+
npm install
18+
19+
# Run tests to verify everything works
20+
npm test
21+
```
22+
23+
## If npm install fails
24+
25+
If you encounter integrity check errors with the `obsidian` package:
26+
27+
```bash
28+
# Clear npm cache and reinstall
29+
npm cache clean --force
30+
rm -rf node_modules package-lock.json
31+
npm install
32+
```
33+
34+
This should resolve the issue. The npm cache fix worked for us!
35+
36+
## Test Files Created
37+
38+
- `test/frontmatter-exists.test.ts` - Comprehensive unit tests for the new feature
39+
- `test/mocks/obsidian.ts` - Mock Obsidian API
40+
- `test/setup.ts` - Jest setup file
41+
- `jest.config.js` - Jest configuration
42+
43+
## Running Tests
44+
45+
Once Jest is installed:
46+
47+
```bash
48+
npm test # Run all tests
49+
npm run test:watch # Watch mode
50+
npm run test:coverage # With coverage
51+
```
52+
53+
## What the Tests Cover
54+
55+
The `frontmatter-exists.test.ts` file tests:
56+
- ✅ Non-empty strings
57+
- ❌ Empty strings
58+
- ❌ Whitespace-only strings
59+
- ✅ Non-empty arrays
60+
- ❌ Empty arrays
61+
- ✅ Boolean true
62+
- ✅ Boolean false
63+
- ✅ Number zero
64+
- ❌ Null values
65+
- ❌ Missing/undefined fields
66+
- ❌ Missing frontmatter
67+
- ✅ Nested fields

docs/Concepts.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This plugin was designed to read code blocks in [YAML format](https://en.wikiped
66

77
### Collecting Data
88

9-
Providing parameters `searchType` and `searchTarget` is the minimum requirement for a successful data collection. `searchType` can be `tag`, `frontmatter`, `frontmatterlist`, `wiki`, `dvField`, `table`, `fileMeta`, `task`, or `text`. Then the cooresponding `searchTarget` should be provided according to the specified type.
9+
Providing parameters `searchType` and `searchTarget` is the minimum requirement for a successful data collection. `searchType` can be `tag`, `frontmatter`, `frontmatter.exists`, `frontmatterlist`, `wiki`, `dvField`, `table`, `fileMeta`, `task`, or `text`. Then the cooresponding `searchTarget` should be provided according to the specified type.
1010

1111
### Target Evaluation
1212

docs/InputParameters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ These key-value pairs are placed under the root of the code block.
1818

1919
| Key | Description | Number of Values | Default |
2020
|:--------|:-------|:-----------:|:------|
21-
| `searchType` | Type of `searchTarget` (tag\|frontmatter\|wiki\|text\|dvField\|table\|fileMeta\|task) | 1~NT | Must be provided |
21+
| `searchType` | Type of `searchTarget` (tag\|frontmatter\|frontmatter.exists\|wiki\|text\|dvField\|table\|fileMeta\|task) | 1~NT | Must be provided |
2222
| `searchTarget` | Target to search<br>[[detail](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/TargetEvaluation.md)] | NT (Number of Targets) | Must be provided |
2323
| `folder` | Root path containing notes to search | 1 | Root of this vault |
2424
| `file` | Files to include for searching | N | null |

docs/TargetEvaluation.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Target Evaluation
22

3-
From the [input parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md) you provided, the search targets dispersed in the notes will be counted or evaluated as a value. Tracker plugin supports nine kinds of `searchType`: `tag`, `frontmatter`, `frontmatterlist`, `wiki`, `text`, `table`, `dvField`, `task`, and `fileMeta`, dealing with different types of searching condition.
3+
From the [input parameters](https://github.com/pyrochlore/obsidian-tracker/blob/master/docs/InputParameters.md) you provided, the search targets dispersed in the notes will be counted or evaluated as a value. Tracker plugin supports multiple kinds of `searchType`: `tag`, `frontmatter`, `frontmatter.exists`, `frontmatterlist`, `wiki`, `text`, `table`, `dvField`, `task`, and `fileMeta`, dealing with different types of searching condition.
44

55
## Multiple Targets
66
You can provide multiple search targets in code block by entering an array of targets separated by a comma under parameter `searchType` and `searchTarget`. Each of the targets will be identified in order and then the values in notes will be evaluated and form a dataset indexed by that order in the array (zero-based indexing).
@@ -53,6 +53,17 @@ mood: 10<br>
5353
......<br>
5454
\-\-\-<br>
5555

56+
### searchType: frontmatter.exists
57+
58+
This search type tracks days when a frontmatter field exists and is non-empty. Unlike `frontmatter`, which tries to parse the value as a number, `frontmatter.exists` simply checks if the field has any value (text, number, boolean, etc.) and counts it as 1 (or the value specified by `constValue`). This is useful for tracking habits or events where you just need to know if something happened, regardless of the value.
59+
60+
For example, if you have a frontmatter field `meditation: "yes"` or `meditation: "completed"`, using `searchType: frontmatter.exists` with `searchTarget: meditation` will count each day where the field exists and is not empty.
61+
62+
\-\-\-<br>
63+
meditation: yes<br>
64+
......<br>
65+
\-\-\-<br>
66+
5667
### searchType: frontmatterlist
5768
This option is for vaultkeepers who want to use the same custom YAML property to track multiple targets. It is useful for tracking habits or categories recorded as a list in front matter fields *other* than tags.
5869

@@ -66,7 +77,7 @@ When specifying a searchTarget, use the key name followed by the member value in
6677
searchType: frontmatterlist
6778
searchTarget: habits[spanish]
6879
```
69-
When the member value is present in the list, it will be evaluated as a constant value (default 1.0). When it is absent, the day will have no value. This is ideal for counting occurances.
80+
When the member value is present in the list, it will be evaluated as a constant value (default 1.0). When it is absent, the day will have no value. This is ideal for counting occurances.
7081

7182
#### Formatting property values in frontmatter
7283

@@ -88,7 +99,6 @@ habits:
8899
- piano
89100
```
90101

91-
92102
### searchType: wiki
93103
This search type helps you count wiki links in articles. For example,
94104
[[A]]

examples/TestFrontmatter.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ month:
6565
initMonth: 2021-01
6666
```
6767

68+
## Track Field Existence (frontmatter.exists)
69+
70+
Track days when a frontmatter field exists and is non-empty, regardless of the value. This is useful for habit tracking where you just need to know if something happened.
71+
72+
``` tracker
73+
searchType: frontmatter.exists
74+
searchTarget: meditation
75+
datasetName: Meditation Days
76+
folder: diary
77+
startDate: 2021-01-01
78+
endDate: 2021-01-31
79+
month:
80+
mode: circle
81+
```
82+
83+
This will count each day where the `meditation` field exists in the frontmatter, whether it's `meditation: yes`, `meditation: completed`, `meditation: true`, or any other non-empty value.
84+
6885
Please also check those search targets in markdown files under folder 'diary'.
6986

7087

examples/diary/05-01-2021.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
tags: work_log
33
mood: 8
4+
meditation: true
45
---
56

67
#weight:60.2kg

examples/diary/05.01.2021.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
tags: work_log
33
mood: 8
4+
meditation: true
45
---
56

67
#weight:60.2kg

0 commit comments

Comments
 (0)