- Thank you
- Updating documentation
- Updating code
- Local setup and workflow for changes to code and tests
- Maintaining the tests
- Dependency Upgrades and Repository Maintenance
- FAQs
Thank you for wanting to contribute to Obsidian Tasks! Every contribution is much appreciated!
The documentation resides under the ./docs
directory.
It consists of markdown files, which Jekyll will transform into web pages that you can view at https://obsidian-tasks-group.github.io/obsidian-tasks/ .
In the simplest case, you can update the existing markdown file and create a pull request (PR) with your changes.
For documentation changes to show up at https://obsidian-tasks-group.github.io/obsidian-tasks/ , they must be in the gh-pages
branch.
If you want to see your changes available immediately and not only after the next release, you should make your changes on the gh-pages
branch.
When you create a PR, it should merge into the gh-pages
branch as well.
If you document an unreleased feature, you should update the documentation on main
instead. Ideally together with the related code changes.
If this is confusing, don't worry.
We will help you make this right once you opened the PR.
Add the following between the H1 and the first H2, to show a table of contents in a page on the published documentation.
{: .no_toc }
<details open markdown="block">
<summary>
Table of contents
</summary>
{: .text-delta }
1. TOC
{:toc}
</details>
---
Linking to other pages in the documentation is non-obvious and a bit tedious.
Here are some examples to copy-and-paste:
To pages:
[‘Create or edit Task’ Modal]({{ site.baseurl }}{% link getting-started/create-or-edit-task.md %})
[Dates]({{ site.baseurl }}{% link getting-started/dates.md %})
[Filters]({{ site.baseurl }}{% link queries/filters.md %})
[Global Filter]({{ site.baseurl }}{% link getting-started/global-filter.md %})
[Priorities]({{ site.baseurl }}{% link getting-started/priority.md %})
[Recurring Tasks]({{ site.baseurl }}{% link getting-started/recurring-tasks.md %})
To sections:
[due]({{ site.baseurl }}{% link getting-started/dates.md %}#-due)
[scheduled]({{ site.baseurl }}{% link getting-started/dates.md %}#-scheduled)
[start]({{ site.baseurl }}{% link getting-started/dates.md %}#-start)
For readability and accessibility, images should be created:
- Set the Obsidian window size to be around 1500 pixels wide about between 700 and 1100 pixels high.
- Using the Default Obsidian theme.
- In the Light colour scheme.
- With a large font size.
- With as little blank or dead space as possible around the area of focus.
Saving images:
- Save them in .PNG format.
- Save them in docs/images/.
When embedding an image inside a documentation page, please link to the local file and include a brief summary underneath.
For example, to embed the acme.png
file in the documentation:
![ACME Tasks](images/acme.png)
The `ACME` note has some tasks - as linked to from `docs/index.md`.
or
![ACME Tasks](../images/acme.png)
The `ACME` note has some tasks - as linked to from any file in a sub-directory of `docs/`.
With this mechanism, you can preview the embedded images in any decent Markdown editor, including by opening the obsidian-tasks
directory in Obsidian.
We have introduced version markers to the documentation, to show users in which Tasks version a specific feature was introduced. This means that newly written documentation should be tagged with a placeholder, which will be replaced with the actual version upon release.
There are 2 styles of placeholders used through the documentation, Please pick the one that fits your text better. (If in doubt, take a look at the existing version tags for other features.)
> Introduced in Tasks X.Y.Z
- This placeholder is usually used after a section heading.
> X (Y and Z) was introduced in Tasks X.Y.Z
- This placeholder is used when you need to tag a sub-part of something, for example a list.
We use GitHub pages for our documentation. You can read more about it at their official documentation.
To generate the documentation site on your machine, see docs/README.md.
Ideally, an issue or Discussion already exists and we discussed your implementation in that issue before you open the pull request (PR). This is not mandatory, but it helps improve the process and reduce unnecessary back-and-forth.
Once you want to propose your changes, create a PR and we'll have a look when we have time. Discussion will take place inside the PR.
If you can, please add/update tests and documentation where appropriate.
This project uses the Yarn package manager for Javascript, and several formatting and linting tools. Below are specific setup instructions for changing code and tests, as well as tips for local development workflow.
This project uses Node 14.x, if you also use a different version, look at using nvm
to manage your Node versions.
If you are using nvm
, you can install the 14.x version of Node with nvm install 14; nvm use <full version number you installed such as 14.19.3>
.
To setup the local environment after cloning the repository, run the following commands:
yarn
yarn build
yarn test
yarn lint
yarn lint:markdown
Make sure you build, test and lint before pushing to the repository. Lefthook is used to cover these checks before commit and push.
When developing locally, you can use the yarn dev
command to start a development build.
This will cause a rebuild of the code base every time you make a change so you can see if there are any code errors.
Not all the functionality of Tasks can be tested via the automated tests.
If you want to test your changes in a local obsidian vault use yarn run build:dev
.
This will generate the main.js
in the root of the repository with a sourcemap in it to facilitate
debugging using the development console (Ctrl+Shift+i
on Windows or Cmd+Shift+i
on Mac) in Obsidian.
Then either manually copy the main.js
file to local test vault's .obsidian/plugins/obsidian-tasks
folder, or use the Powershell script that is run via the yarn deploy:local
command to
create a symbolic link to the plugins folder for this plugin (obsidian-tasks-plugin
).
If you manually copy, you must remember to copy the new version over after every build.
With the symbolic link, whenever a build occurs using yarn run dev
or yarn run build:dev
the plugin will be updated
in the obsidian vault you are targeting using the OBSIDIAN_PLUGIN_ROOT
environment variable.
It is recommended you use the Hot-Reload plugin in that vault also;
it will automatically reload the plugin when files change.
The script run by deploy:local
will create a .hotreload
file in the root of the repository to assist.
The tests use the ts-jest wrapper around the jest test framework.
The Expect page is a good reference for the many jest testing features.
- Tests that test low-level implementation details are hard to maintain over time. Instead, test user-visible features.
- Try to think of the purpose of the code that has missing tests.
- For example, in
taskFromLine()
insrc/Commands/CreateOrEdit.ts
the comments are quite useful in terms of showing the different scenarios being considered. Something like: already a task line with a global filter, already a task line missing the global filter, already a task line and there is no global filter, already a bullet item, not in a bullet item - These then would be good tests to write: specifically, tests to check that each of those scenarios does actually behave as expected.
- And if the implementation changed in future, those tests would be extremely useful to the maintainer at the time.
- And if a new behaviour was added in future, it would be obvious how to add a new test for it.
- For example, in
Often, untested code is in locations that you can't call in tests (for example, because it uses some Obsidian code). All that needs to be done then is to refactor - via 'move method' or 'extract method') the code out to a different source file. For more about refactoring safely and easily, see the talk Refactoring Superpowers: Make Your IDE Do Your Work, Faster and More Safely.
If you struggle to name a Jest it
test, think in terms of should: for example, should convert a line with no bullet to ...
For testing more complex objects, some of the tests here use Jest's Snapshot Testing facility, which is similar to Approval Tests but easier to use in JavaScript.
For readability of snapshots, we favour Inline Snapshots, which are saved in the source code. See that documentation for how to easily update the inline snapshot, if the output is intended to be changed.
There is a brief overview of Approval tests at approvaltests.com.
For including complex text in the documentation, some tests here will soon start using the Approval Tests implementation in NodeJS.
If these tests fail, they will currently try and launch diffmerge to show the differences between received and approved files.
Expand Details on Approval Tests
Approval tests typically call a function beginning verify
, and pass
in some text or an object to be tested.
Example test in ApprovalTestsDemo.test
, that saves its input in a text file:
test('SimpleVerify', () => {
verify('Hello From Approvals');
});
The corresponding approved
file, named ApprovalTestsDemo.test.ApprovalTests_SimpleVerify.approved.txt
:
Hello From Approvals
test('JsonVerify', () => {
const data = { name: 'fred', age: 30 };
verifyAsJson(data);
});
The corresponding approved
file, named ApprovalTestsDemo.test.ApprovalTests_JsonVerify.approved.json
:
{
"name": "fred",
"age": 30
}
The WebStorm IDE has a helpful page on how it makes testing with jest easy.
Note in particular the Snapshot testing section for how to view the diffs in the event of snapshot test failures, and also how to update the saved snapshot of one or all snapshot failures.
yarn run jest --coverage
will generate a coverage report in the coverage
directory, which is ignored by this project's .gitignore
.
Your IDE may also be able to show you the test coverage of a source file.
Adding tests where possible - see Location of code for constraints to code not currently covered by the automated tests is a great way to contribute!
Dependencies for the plugin are set using the package.json
(human-editable) and yarn.lock
(machine-generated) files.
After any change to dependencies in package.json
, run yarn
to update the yarn.lock
file and commit the changes in both files.
If you see a warning from yarn
about a missing peer dependency that mentions obsidian
or @codemirror
, you can safely ignore it. Other yarn messages should likely be resolved before commit.
Code changes that also involve dependency changes may require additional testing and review.
Please only change package.json
if necessary for your contribution.
Package management for the documentation site is handled separately; see the documentation site README for details on that.
The rest of this section is most useful for maintainers of the repository and not necessary for typical documentation, code, or test contributions.
Expand Details on Dependency Upgrades and Dependabot
Keeping dependencies up to date ensures the best experience and security for Tasks plugin users. This project uses Dependabot to help automate dependency updates, but some dependencies require manual testing before they can be merged. See the FAQ entry on smoke-testing for how to manually test.
Multiple depdendency upgrades can be smoke-tested together in a batch.
An easy way to do this is to make a local branch that merges only the changes to the package.json
from each of the different upgrade PRs (either manually by examining the change to package.json
in each PR and then editing your local version to match, or via git).
Then run yarn
to update the yarn.lock
file, and this should avoid merge conflicts.
After linting, testing, and smoke-testing (using yarn build:dev
and manually copying the main.js
to the local test vault), you can merge the individual upgrade PRs (leaving time for dependabot to rebase them between each individual merge) and delete the local branch.
The package.json
file is the human-editable interface for dependency management.
Acceptable dependency versions are specified using semver version ranges.
This project pins certain dependencies to exact version numbers, and others to major version ranges.
After any chance to the package.json
file, either by manual edit or by a command such as yarn add -D newDevDependency
and then saving the file, run yarn
to update the yarn.lock
file.
The yarn.lock
file is machine-generated - based on the version constraints in package.json
- and should not be edited manually.
It lists the specific versions of all installed dependencies (and their dependencies, recursively)
currently used by the project.
If there are ever git merge conflicts in yarn.lock
, best practice is to rename the conflicted file, ensure package.json
is correct, and then run yarn
to regenerate the yarn.lock
file.
The newly-generated file can then be committed to resolve the merge conflict.
package.json
separates dependencies only used in the development, testing, and building process ("devDependencies") from those contained as part of the plugin's main.js
file because they are used at runtime ("dependencies").
However, Obsidian's plugin architecture handles linking in the Obsidian API and its dependencies (such as @codemirror/*
packages), so those are not part of the runtime "dependencies" in package.json
and must also be marked as "external" in the build system configuration (esbuild.config.mjs
).
Some subset of the packages marked "external" in esbuild.config.mjs
will be listed as "devDependencies" because their APIs are used in the plugin or its tests.
Therefore, "devDependency" vs. "dependency" separation is not a sufficient indicator of whether a package
needs manual "smoke testing" of runtime behavior.
Look at the package.json
entry for a package and search for which files import the package.
- When in doubt, smoke-test. Smoke-testing of multiple dependency upgrades can be done in a batch, to reduce the time spent on this process.
- Definitely smoke test: Anything that is involved in producing the built output to users. For example:
- everything in the "dependencies" (rather than "devDependencies") list in
package.json
esbuild
(the build system)- anything imported by the esbuild config file
esbuild.config.mjs
(for example,builtin-modules
,svelte-preprocess
) obsidian
(also see Special Cases section below)- all
@codemirror/*
moment
- everything in the "dependencies" (rather than "devDependencies") list in
- Automated testing sufficient: Our linting, formatting, and testing code does not affect the built output and is run automatically on each PR, so it does not need smoke tests. An automated test fail for an upgrade to one of these packages may be an indication of a newly found linter error in the code and should be investigated. However, if all the automatic checks pass, these packages can be merged right away:
markdownlint
- anything with
eslint
in it (including@typescript/eslint*
, which version bump weekly regardless of whether they have any changes) svelte-check
(but not other svelte things, which are used in the build system)- anything with
prettier
lefthook
- anything with
jest
in it (but see the note below on Dependency Groups for details).
- For anything else, where and how is it being used? If it's only in tests, or only used by developers, no need to smoke test.
Several dependencies come in groups (for example, @typescript/eslint*
or ones containing the word jest
) that may need to be updated together.
For example, ts-jest
relies on having a matching major version with jest
and its types (@types/jest
).
Every jest-related package that shares a major version number with ts-jest
, jest
etc must have an available upgrade to the new major version before any of them can be upgraded.
Otherwise, automated testing may fail due to version mismatch.
Dependabot does not know how to handle groups like this, so the maintainer must keep track of this.
yarn outdated
is a useful command-line tool for seeing whether there are upgrades available.
Dependabot will not offer PRs for every package.
For example, if a package is pinned to an exact version (for example,obsidian
) that is far behind the current, dependabot may not give any notification of an upgrade.
Use yarn outdated
every so often to see if there are any upgrades available.
Note that yarn's upgrade
and upgrade-interactive
commands will respect the project's package.json
which pins either major versions or a specific number.
yarn outdated
gives a better indication of what upgrades are available.
To upgrade major version numbers or a dependency where a fixed version is used, you must
manually edit the package.json
file. Note: Remember yarn
after any edits to package.json
to ensure the yarn.lock
file is updated.
Updates to the obsidian
package may require additional changes to manifest.json
and versions.json
and should be handled with care so that Tasks users who are not on the latest version of Obsidian have time to update.
Click the "Expand Details" line above to expand or close the section.
You can toggle a task‘s status by:
- using the command (may be bound to a hotkey),
- clicking on a checkbox of an inline task in Reading mode,
- clicking on a checkbox of an inline task in Live Preview, or
- clicking on a checkbox in query results (same for Reading mode and Live Preview).
The code is located as follows:
- For 1.:
./src/Commands/ToggleDone.ts
- Numbers 2. and 4. use a checkbox created by
TaskLineRenderer.renderTaskLine
. There, the checkbox gets a click event handler. - For 3.:
./src/LivePreviewExtension.ts
Toggle behavior:
- Number 1. toggles the line directly where the cursor is in the file inside Obsidian's vault.
- The click event listener of 2. and 4. uses
File::replaceTaskWithTasks()
. That, in turn, updates the file in Obsidian‘s Vault (like 1, but it needs to find the correct line). - Number 3. toggles the line directly where the checkbox is on the "document" of CodeMirror (the library that Obsidian uses to show text on screen). That, in turn, updates the file in Obsidian's Vault.
Obsidian writes the changes to disk at its own pace.
- In tests/Task.test.ts:
- Add a new failing block to the
'identicalTo'
section. - Here is an existing example: 'should check path'.
- Add a new failing block to the
- In src/Task.ts, update
Task.identicalTo()
:- Once you have a failing test in
Task.test.ts
, implement the check for changed value of your new field inTask.identicalTo()
. - This important method is used to detect whether any edits of any kind have been made to a task, to detect whether task block results need to be updated.
- Here is the code for the method as of 2022-11-12:
- Once you have a failing test in
- In tests/TestingTools/TaskBuilder.ts:
- Add the new field and a corresponding method.
- Keep the same field order as in the
Task
class. - Update the
build()
method.
- In tests/TestingTools/TaskBuilder.test.ts:
- If the code in TaskBuild will be non-trivial, first add a failing test for it.
All the following steps would be done in the same branch, for inclusion in the same pull request.
- Implement the search filter:
- Add to src/Query/Filter a new class that inherits Field
- Typically, this can be done by inheriting one of the partial implementations:
- Add the new class to src/Query/FilterParser.ts
Write tests as you go.
Ideally, write a failing test first, and then implement the minimum code for the failing test to pass.
For help on writing and running the tests, see Maintaining the tests
- Add to tests/Query/Filter a new test file.
- This should focus on testing whether or not individual Task objects, with carefully selected sample date, match the filter.
- Think about edge cases.
- Add the new instruction(s) to 'Query parsing' test in tests/Query.test.ts
- This verifies that the new filter instruction has been correctly wired in to the Query class.
It can be worth writing the documentation first, to ensure that you can explain the new feature clearly before implementing it.
For help on editing the documentation, see Updating documentation
- Document the new instruction(s) in docs/queries/filters.md
- Add the placeholder to indicate which version the feature will be released in: see Version numbers in documentation
- Add the new instruction(s) to docs/quick-reference/index.md
- #1098 feat: Add filename filter
- This shows adding a brand new Field class, so shows all the steps above.
- #1228 feat: Add 4 instructions: '(done|due|date|start) date is invalid'
- This adds several new instructions via the DateField class, which implements most of the date-based filters.
- It was sufficient to add tests of the new feature in just one of the instructions implemented via DateField.
- It also shows adding a file to the sample vault, to demonstrate and test the new feature.
First...
- Go to the Verify Commit actions page.
- Click on the build of the code version you want to test. For example, you might click on the build for a particular pull request, or the most recent build on
main
.
Then do one of the following options...
- In the Artifacts section at the bottom, click on the link whose name starts with Tasks-Demo-..., for example Tasks-Demo-VerifyCommit-Build1367-Run1.
- This will download a zip file containing a copy of the Tasks-Demo sample vault, including the build of the plugin.
- The numbers in the file name will vary.
- Optionally, rename the zip file to give it a meaningful name.
- For example, you could append 'testing PR 1234 - nicer styling'.
- Expand the zip file.
- It will create a folder of the same name.
- Open the expanded folder in Obsidian:
- Open Obsidian
- Click 'Open another vault' button
- Click 'Open folder as vault' button
- Navigate to the downloaded folder
- Click 'Open'
You can use these steps to install the built plugin either in to the Tasks-Demo vault inside a clone of the obsidian-tasks repo or in to one of your own vaults.
- In the Artifacts section at the bottom, click on dist-verified to download a build of the plugin.
- Optionally, rename the zip file to give it a meaningful name.
- For example, you could append 'testing PR 1234 - nicer styling'.
- Expand the downloaded zip file
- Copy the files in the expanded folder to the
.obsidian/plugins/obsidian-tasks-plugin/
folder in your vault, over-writing the previous files. - Restart Obsidian.
Follow the steps in resources/sample_vaults/Tasks-Demo/Manual Testing/Smoke Testing the Tasks Plugin.md
.
- Go to the CodeQL Actions page and click
Run workflow
to run the CodeQL checks- Wait for them to succeed
- If they fail, put the release on hold and fix any issues first. (Failures are very rare.)
- Check out the
main
branch - Check for the current version in
package.json
(for example,1.4.1
) and decide on a next version- Backwards incompatible change: increase major version
- New functionality: increase minor version
- Only bug fixes: increase patch version
- Having decided on the new version, replace all
X.Y.Z
in the documentation with the new version number. - Check the current version of the obsidian dependency in
package.json
(for example,0.13.21
) - Run
./release.sh <new tasks version> <obsidian version>
- Make sure there are no uncommitted changes. Stash them if necessary.
- Wait for GitHub Actions to create the new release
- Update the release description with the changes of the release, which will be a Draft.
- On the release page, GitHub provides a button to auto-generate release notes which works nicely as a good starting point.
- When you are happy with the release notes, hit the Publish button.
- At this point, GitHub will send an email automatically to everyone who is subscribed to Tasks releases.
- Optional: Post to
- Obsidian Discord
- Add a post in the
#updates
channel, with detail about the release - Add a one-liner in the
#task-management
channel, linking to the first post
- Add a post in the
- r/ObsidianMD on Reddit
- Obsidian Forum Share & Showcase section
- etc.
- Obsidian Discord
These are markdown files written for contributors, and intended to be viewed on GitHub. To make it easy to see their structure, they have a machine-generated Table of Contents ("ToC").
The ToCs will eventually be automated automatically via GitHub Actions, but for now, the following needs to be done in order to update them:
- Install MarkdownSnippets, also known as
mdsnippets
- Run:
mdsnippets && yarn run lint:markdown && git add --renormalize .
The background to this is in PR #1248.